diff --git a/.travis.yml b/.travis.yml index 77acce14c..a73caf157 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,13 +5,15 @@ jdk: android: components: - tools - - build-tools-28.0.3 - - android-28 + - build-tools-28.0.3 # Temporary, used by I2P + - build-tools-29.0.2 + - android-28 # Temporary, used by I2P + - android-29 licenses: - 'android-sdk-license-.+' - '.*intel.+' before_install: - - yes | sdkmanager "platforms;android-28" + - yes | sdkmanager "platforms;android-29" - chmod +x gradlew - git submodule update --init --recursive install: diff --git a/CHANGELOG.md b/CHANGELOG.md index eea7b1f13..289a9a31f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Change Log ========== +Version 5.0.2 *(2019-09-07)* +---------------------------- +- Target Android 10 API 29 +- Update desktop and mobile user agents +- Fixed segfault that occurred when some URLs were clicked +- Added dialog to show SSL certificate info when SSL icon is tapped + Version 5.0.1 *(2019-09-01)* ---------------------------- - 64 bit I2P support diff --git a/app/build.gradle b/app/build.gradle index b901de884..2115436f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,7 +13,7 @@ android { defaultConfig { minSdkVersion project.minSdkVersion targetSdkVersion project.targetSdkVersion - versionName project.versionName + versionName "5.0.2" vectorDrawables.useSupportLibrary = true } @@ -60,14 +60,14 @@ android { dimension "capabilities" buildConfigField "boolean", "FULL_VERSION", "Boolean.parseBoolean(\"true\")" applicationId "acr.browser.lightning" - versionCode project.versionCode_plus + versionCode 100 } lightningLite { dimension "capabilities" buildConfigField "boolean", "FULL_VERSION", "Boolean.parseBoolean(\"false\")" applicationId "acr.browser.barebones" - versionCode project.versionCode_lite + versionCode 101 } } @@ -112,13 +112,13 @@ dependencies { // support libraries implementation "androidx.palette:palette:1.0.0" implementation "androidx.annotation:annotation:1.1.0" - implementation "androidx.vectordrawable:vectordrawable-animated:1.0.0" - implementation "androidx.appcompat:appcompat:1.0.2" + implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0" + implementation "androidx.appcompat:appcompat:1.1.0" implementation "com.google.android.material:material:1.0.0" implementation "androidx.recyclerview:recyclerview:1.0.0" - implementation "androidx.core:core:1.0.2" + implementation "androidx.core:core:1.1.0" implementation "androidx.constraintlayout:constraintlayout:1.1.3" - implementation "androidx.fragment:fragment:1.0.0" + implementation "androidx.fragment:fragment:1.1.0" implementation "androidx.drawerlayout:drawerlayout:1.0.0" // html parsing for reading mode @@ -154,7 +154,7 @@ dependencies { // rx implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - implementation 'io.reactivex.rxjava2:rxjava:2.2.11' + implementation 'io.reactivex.rxjava2:rxjava:2.2.12' implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' // tor proxy @@ -171,7 +171,7 @@ dependencies { // kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" - implementation 'androidx.core:core-ktx:1.2.0-alpha03' + implementation 'androidx.core:core-ktx:1.2.0-alpha04' } kapt { diff --git a/app/src/main/java/acr/browser/lightning/AppTheme.kt b/app/src/main/java/acr/browser/lightning/AppTheme.kt new file mode 100644 index 000000000..a65150f7b --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/AppTheme.kt @@ -0,0 +1,12 @@ +package acr.browser.lightning + +import acr.browser.lightning.preference.IntEnum + +/** + * The available app themes. + */ +enum class AppTheme(override val value: Int) : IntEnum { + LIGHT(0), + DARK(1), + BLACK(2) +} diff --git a/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt b/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt index e2f95cce0..7b6797eba 100644 --- a/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt +++ b/app/src/main/java/acr/browser/lightning/IncognitoActivity.kt @@ -12,6 +12,8 @@ import io.reactivex.Completable class IncognitoActivity : BrowserActivity() { + override fun provideThemeOverride(): Int? = R.style.Theme_DarkTheme + @Suppress("DEPRECATION") public override fun updateCookiePreference(): Completable = Completable.fromAction { val cookieManager = CookieManager.getInstance() diff --git a/app/src/main/java/acr/browser/lightning/adblock/NoOpAdBlocker.kt b/app/src/main/java/acr/browser/lightning/adblock/NoOpAdBlocker.kt index 833b741cc..5e9816796 100644 --- a/app/src/main/java/acr/browser/lightning/adblock/NoOpAdBlocker.kt +++ b/app/src/main/java/acr/browser/lightning/adblock/NoOpAdBlocker.kt @@ -1,12 +1,12 @@ package acr.browser.lightning.adblock +import dagger.Reusable import javax.inject.Inject -import javax.inject.Singleton /** * A no-op ad blocker implementation. Always returns false for [isAd]. */ -@Singleton +@Reusable class NoOpAdBlocker @Inject constructor() : AdBlocker { override fun isAd(url: String) = false diff --git a/app/src/main/java/acr/browser/lightning/adblock/source/PreferencesHostsDataSourceProvider.kt b/app/src/main/java/acr/browser/lightning/adblock/source/PreferencesHostsDataSourceProvider.kt index 178516575..99d0b1522 100644 --- a/app/src/main/java/acr/browser/lightning/adblock/source/PreferencesHostsDataSourceProvider.kt +++ b/app/src/main/java/acr/browser/lightning/adblock/source/PreferencesHostsDataSourceProvider.kt @@ -5,6 +5,7 @@ import acr.browser.lightning.log.Logger import acr.browser.lightning.preference.UserPreferences import android.app.Application import android.content.res.AssetManager +import dagger.Reusable import io.reactivex.Single import okhttp3.OkHttpClient import javax.inject.Inject @@ -12,6 +13,7 @@ import javax.inject.Inject /** * A [HostsDataSourceProvider] backed by [UserPreferences]. */ +@Reusable class PreferencesHostsDataSourceProvider @Inject constructor( private val userPreferences: UserPreferences, private val assetManager: AssetManager, diff --git a/app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.kt b/app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.kt index b96bd3c45..ea0f7a1ae 100644 --- a/app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.kt +++ b/app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.kt @@ -11,7 +11,7 @@ import acr.browser.lightning.html.bookmark.BookmarkPageFactory import acr.browser.lightning.html.homepage.HomePageFactory import acr.browser.lightning.log.Logger import acr.browser.lightning.preference.UserPreferences -import acr.browser.lightning.ssl.SSLState +import acr.browser.lightning.ssl.SslState import acr.browser.lightning.view.BundleInitializer import acr.browser.lightning.view.LightningView import acr.browser.lightning.view.TabInitializer @@ -77,7 +77,7 @@ class BrowserPresenter( private fun onTabChanged(newTab: LightningView?) { logger.log(TAG, "On tab changed") - view.updateSslState(newTab?.currentSslState() ?: SSLState.None) + view.updateSslState(newTab?.currentSslState() ?: SslState.None) sslStateSubscription?.dispose() sslStateSubscription = newTab 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 8babc5006..e95891c86 100644 --- a/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt +++ b/app/src/main/java/acr/browser/lightning/browser/BrowserView.kt @@ -1,6 +1,6 @@ package acr.browser.lightning.browser -import acr.browser.lightning.ssl.SSLState +import acr.browser.lightning.ssl.SslState import android.view.View import androidx.annotation.StringRes @@ -16,7 +16,7 @@ interface BrowserView { fun updateTabNumber(number: Int) - fun updateSslState(sslState: SSLState) + fun updateSslState(sslState: SslState) fun closeBrowser() diff --git a/app/src/main/java/acr/browser/lightning/browser/ProxyChoice.kt b/app/src/main/java/acr/browser/lightning/browser/ProxyChoice.kt new file mode 100644 index 000000000..ed89b18bf --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/browser/ProxyChoice.kt @@ -0,0 +1,13 @@ +package acr.browser.lightning.browser + +import acr.browser.lightning.preference.IntEnum + +/** + * The available proxy choices. + */ +enum class ProxyChoice(override val value: Int) : IntEnum { + NONE(0), + ORBOT(1), + I2P(2), + MANUAL(3) +} diff --git a/app/src/main/java/acr/browser/lightning/browser/SearchBoxDisplayChoice.kt b/app/src/main/java/acr/browser/lightning/browser/SearchBoxDisplayChoice.kt new file mode 100644 index 000000000..0f35b6cc3 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/browser/SearchBoxDisplayChoice.kt @@ -0,0 +1,12 @@ +package acr.browser.lightning.browser + +import acr.browser.lightning.preference.IntEnum + +/** + * An enum representing what detail level should be displayed in the search box. + */ +enum class SearchBoxDisplayChoice(override val value: Int) : IntEnum { + DOMAIN(0), + URL(1), + TITLE(2) +} diff --git a/app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.kt b/app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.kt index c04063689..82af137fa 100644 --- a/app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.kt +++ b/app/src/main/java/acr/browser/lightning/browser/SearchBoxModel.kt @@ -5,11 +5,13 @@ import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.utils.Utils import acr.browser.lightning.utils.isSpecialUrl import android.app.Application +import dagger.Reusable import javax.inject.Inject /** * A UI model for the search box. */ +@Reusable class SearchBoxModel @Inject constructor( private val userPreferences: UserPreferences, application: Application @@ -32,34 +34,21 @@ class SearchBoxModel @Inject constructor( * @param isLoading whether the page is currently loading or not. * @return the string that should be displayed by the search box. */ - fun getDisplayContent(url: String, title: String?, isLoading: Boolean): String { + fun getDisplayContent(url: String, title: String?, isLoading: Boolean): String = when { - url.isSpecialUrl() -> return "" - isLoading -> return url + url.isSpecialUrl() -> "" + isLoading -> url else -> when (userPreferences.urlBoxContentChoice) { - 1 -> { - // URL, show the entire URL - return url - } - 2 -> { - // Title, show the page's title - return if (title?.isEmpty() == false) { + SearchBoxDisplayChoice.DOMAIN -> safeDomain(url) + SearchBoxDisplayChoice.URL -> url + SearchBoxDisplayChoice.TITLE -> + if (title?.isEmpty() == false) { title } else { untitledTitle } - } - 0 -> { - // Default, show only the domain - return safeDomain(url) - } - else -> { - // Default, show only the domain - return safeDomain(url) - } } } - } private fun safeDomain(url: String) = Utils.getDomainName(url) diff --git a/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt b/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt index 3cfb28cee..e7eedcf5d 100644 --- a/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt +++ b/app/src/main/java/acr/browser/lightning/browser/TabsManager.kt @@ -359,7 +359,7 @@ class TabsManager @Inject constructor( .flattenAsObservable { bundle -> bundle.keySet() .filter { it.startsWith(BUNDLE_KEY) } - .map(bundle::getBundle) + .mapNotNull(bundle::getBundle) } .doOnNext { logger.log(TAG, "Restoring previous WebView state now") } 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 a8bf8adee..e6cb6deab 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 @@ -4,13 +4,13 @@ package acr.browser.lightning.browser.activity +import acr.browser.lightning.AppTheme import acr.browser.lightning.IncognitoActivity import acr.browser.lightning.R import acr.browser.lightning.browser.* import acr.browser.lightning.browser.bookmarks.BookmarksDrawerView import acr.browser.lightning.browser.tabs.TabsDesktopView import acr.browser.lightning.browser.tabs.TabsDrawerView -import acr.browser.lightning.constant.LOAD_READING_URL import acr.browser.lightning.controller.UIController import acr.browser.lightning.database.Bookmark import acr.browser.lightning.database.HistoryEntry @@ -34,22 +34,20 @@ import acr.browser.lightning.reading.activity.ReadingActivity import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.search.SuggestionsAdapter import acr.browser.lightning.settings.activity.SettingsActivity -import acr.browser.lightning.ssl.SSLState +import acr.browser.lightning.ssl.SslState +import acr.browser.lightning.ssl.createSslDrawableForState +import acr.browser.lightning.ssl.showSslDialog import acr.browser.lightning.utils.* import acr.browser.lightning.view.* import acr.browser.lightning.view.SearchView import acr.browser.lightning.view.find.FindResults import android.app.Activity import android.app.NotificationManager -import android.content.ClipData import android.content.ClipboardManager import android.content.Intent -import android.content.pm.ActivityInfo import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Color -import android.graphics.PorterDuff -import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.media.MediaPlayer @@ -165,7 +163,6 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr // Image private var webPageBitmap: Bitmap? = null private val backgroundDrawable = ColorDrawable() - private var sslDrawable: Drawable? = null private var incognitoNotification: IncognitoNotification? = null private var presenter: BrowserPresenter? = null @@ -243,7 +240,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr val actionBar = requireNotNull(supportActionBar) //TODO make sure dark theme flag gets set correctly - isDarkTheme = userPreferences.useTheme != 0 || isIncognito() + isDarkTheme = userPreferences.useTheme != AppTheme.LIGHT || isIncognito() shouldShowTabsInDrawer = userPreferences.showTabsInDrawer swapBookmarksAndTabs = userPreferences.bookmarksAndTabsSwapped @@ -255,10 +252,6 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr left_drawer.setLayerType(LAYER_TYPE_NONE, null) right_drawer.setLayerType(LAYER_TYPE_NONE, null) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !shouldShowTabsInDrawer) { - window.statusBarColor = Color.BLACK - } - setNavigationDrawerWidth() drawer_layout.addDrawerListener(DrawerLocker()) @@ -314,7 +307,12 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr // create the search EditText in the ToolBar searchView = customView.findViewById(R.id.search).apply { - setCompoundDrawablesWithIntrinsicBounds(sslDrawable, null, null, null) + search_ssl_status.setOnClickListener { + tabsManager.currentTab?.let { tab -> + tab.sslCertificate?.let { showSslDialog(it, tab.currentSslState()) } + } + } + search_ssl_status.updateVisibilityForContent() search_refresh.setImageResource(R.drawable.ic_action_refresh) val searchListener = SearchListenerClass() @@ -322,7 +320,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr onFocusChangeListener = searchListener setOnEditorActionListener(searchListener) onPreFocusListener = searchListener - addTextChangedListener(searchListener) + addTextChangedListener(StyleRemovingTextWatcher()) initializeSearchSuggestions(this) } @@ -337,7 +335,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr searchBackground = customView.findViewById(R.id.search_container).apply { // initialize search background color - background.setColorFilter(getSearchBarColor(primaryColor, primaryColor), PorterDuff.Mode.SRC_IN) + background.tint(getSearchBarColor(primaryColor, primaryColor)) } drawer_layout.setDrawerShadow(R.drawable.drawer_right_shadow, GravityCompat.END) @@ -409,19 +407,9 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr private inner class SearchListenerClass : OnKeyListener, OnEditorActionListener, OnFocusChangeListener, - SearchView.PreFocusListener, - TextWatcher { - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit - - override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit - - override fun afterTextChanged(e: Editable) { - e.getSpans(0, e.length, CharacterStyle::class.java).forEach(e::removeSpan) - e.getSpans(0, e.length, ParagraphStyle::class.java).forEach(e::removeSpan) - } + SearchView.PreFocusListener { override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean { - when (keyCode) { KeyEvent.KEYCODE_ENTER -> { searchView?.let { @@ -467,11 +455,12 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr // Hack to make sure the text gets selected (v as SearchView).selectAll() - searchView?.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null) + search_ssl_status.visibility = GONE search_refresh.setImageResource(R.drawable.ic_action_delete) } if (!hasFocus) { + search_ssl_status.updateVisibilityForContent() searchView?.let { inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0) } @@ -520,7 +509,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr } private fun setNavigationDrawerWidth() { - val width = resources.displayMetrics.widthPixels - Utils.dpToPx(56f) + 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 @@ -721,7 +710,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr } R.id.action_copy -> { if (currentUrl != null && !currentUrl.isSpecialUrl()) { - clipboardManager.primaryClip = ClipData.newPlainText("label", currentUrl) + clipboardManager.copyToClipboard(currentUrl) snackbar(R.string.message_link_copied) } return true @@ -750,9 +739,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr } R.id.action_reading_mode -> { if (currentUrl != null) { - val read = Intent(this, ReadingActivity::class.java) - read.putExtra(LOAD_READING_URL, currentUrl) - startActivity(read) + ReadingActivity.launch(this, currentUrl) } return true } @@ -823,7 +810,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr private fun showFindInPageControls(text: String) { search_bar.visibility = VISIBLE - findViewById(R.id.search_query).text = "'$text'" + findViewById(R.id.search_query).text = resources.getString(R.string.search_in_page_query, text) findViewById(R.id.button_next).setOnClickListener(this) findViewById(R.id.button_back).setOnClickListener(this) findViewById(R.id.button_quit).setOnClickListener(this) @@ -867,22 +854,16 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr tabsView?.tabsInitialized() } - override fun updateSslState(sslState: SSLState) { - sslDrawable = when (sslState) { - is SSLState.None -> null - is SSLState.Valid -> { - val bitmap = DrawableUtils.createImageInsetInRoundedSquare(this, R.drawable.ic_secured) - val securedDrawable = BitmapDrawable(resources, bitmap) - securedDrawable - } - is SSLState.Invalid -> { - val bitmap = DrawableUtils.createImageInsetInRoundedSquare(this, R.drawable.ic_unsecured) - val unsecuredDrawable = BitmapDrawable(resources, bitmap) - unsecuredDrawable - } + override fun updateSslState(sslState: SslState) { + search_ssl_status.setImageDrawable(createSslDrawableForState(sslState)) + + if (searchView?.hasFocus() == false) { + search_ssl_status.updateVisibilityForContent() } + } - searchView?.setCompoundDrawablesWithIntrinsicBounds(sslDrawable, null, null, null) + private fun ImageView.updateVisibilityForContent() { + drawable?.let { visibility = VISIBLE } ?: run { visibility = GONE } } override fun tabChanged(tab: LightningView) { @@ -933,7 +914,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr mainHandler.postDelayed(drawer_layout::closeDrawers, 200) } - override fun showBlockedLocalFileDialog(onPositiveClick: Function0) = + override fun showBlockedLocalFileDialog(onPositiveClick: Function0) { AlertDialog.Builder(this) .setCancelable(true) .setTitle(R.string.title_warning) @@ -941,6 +922,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.action_open) { _, _ -> onPositiveClick.invoke() } .resizeAndShow() + } override fun showSnackbar(@StringRes resource: Int) = snackbar(resource) @@ -1044,14 +1026,12 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr ui_layout.doOnLayout { // TODO externalize the dimensions val toolbarSize = if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { - // In portrait toolbar should be 56 dp tall - Utils.dpToPx(56f) + R.dimen.toolbar_height_portrait } else { - // In landscape toolbar should be 48 dp tall - Utils.dpToPx(52f) + R.dimen.toolbar_height_landscape } toolbar.layoutParams = (toolbar.layoutParams as ConstraintLayout.LayoutParams).apply { - height = toolbarSize + height = dimen(toolbarSize) } toolbar.minimumHeight = toolbarSize toolbar.doOnLayout { setWebViewTranslation(toolbar_layout.height.toFloat()) } @@ -1223,12 +1203,13 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr backgroundDrawable.color = animatedColor mainHandler.post { window.setBackgroundDrawable(backgroundDrawable) } } else { - tabBackground?.setColorFilter(animatedColor, PorterDuff.Mode.SRC_IN) + tabBackground?.tint(animatedColor) } currentUiColor = animatedColor toolbar_layout.setBackgroundColor(animatedColor) - searchBackground?.background?.setColorFilter(DrawableUtils.mixColor(interpolatedTime, - startSearchColor, finalSearchColor), PorterDuff.Mode.SRC_IN) + searchBackground?.background?.tint( + DrawableUtils.mixColor(interpolatedTime, startSearchColor, finalSearchColor) + ) } } animation.duration = 300 @@ -1450,11 +1431,6 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr }, FILE_CHOOSER_REQUEST_CODE) } - override fun onShowCustomView(view: View, callback: CustomViewCallback) { - originalOrientation = requestedOrientation - onShowCustomView(view, callback, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) - } - override fun onShowCustomView(view: View, callback: CustomViewCallback, requestedOrientation: Int) { val currentTab = tabsManager.currentTab if (customView != null) { @@ -1757,7 +1733,7 @@ abstract class BrowserActivity : ThemableBrowserActivity(), BrowserView, UIContr */ private fun setIsLoading(isLoading: Boolean) { if (searchView?.hasFocus() == false) { - searchView?.setCompoundDrawablesWithIntrinsicBounds(sslDrawable, null, null, null) + search_ssl_status.updateVisibilityForContent() search_refresh.setImageResource(if (isLoading) R.drawable.ic_action_delete else R.drawable.ic_action_refresh) } } diff --git a/app/src/main/java/acr/browser/lightning/browser/activity/StyleRemovingTextWatcher.kt b/app/src/main/java/acr/browser/lightning/browser/activity/StyleRemovingTextWatcher.kt new file mode 100644 index 000000000..d8e95e26c --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/browser/activity/StyleRemovingTextWatcher.kt @@ -0,0 +1,20 @@ +package acr.browser.lightning.browser.activity + +import android.text.Editable +import android.text.TextWatcher +import android.text.style.CharacterStyle +import android.text.style.ParagraphStyle + +/** + * A [TextWatcher] That removes text styling when text is pasted into the view. + */ +class StyleRemovingTextWatcher : TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit + + override fun afterTextChanged(e: Editable) { + e.getSpans(0, e.length, CharacterStyle::class.java).forEach(e::removeSpan) + e.getSpans(0, e.length, ParagraphStyle::class.java).forEach(e::removeSpan) + } +} diff --git a/app/src/main/java/acr/browser/lightning/browser/activity/ThemableBrowserActivity.kt b/app/src/main/java/acr/browser/lightning/browser/activity/ThemableBrowserActivity.kt index 08d6b9ac5..8ed76266a 100644 --- a/app/src/main/java/acr/browser/lightning/browser/activity/ThemableBrowserActivity.kt +++ b/app/src/main/java/acr/browser/lightning/browser/activity/ThemableBrowserActivity.kt @@ -1,5 +1,6 @@ package acr.browser.lightning.browser.activity +import acr.browser.lightning.AppTheme import acr.browser.lightning.R import acr.browser.lightning.di.injector import acr.browser.lightning.preference.UserPreferences @@ -9,6 +10,7 @@ import android.graphics.Color import android.os.Build import android.os.Bundle import android.view.Menu +import androidx.annotation.StyleRes import androidx.appcompat.app.AppCompatActivity import androidx.core.content.withStyledAttributes import androidx.core.graphics.drawable.DrawableCompat @@ -20,21 +22,28 @@ abstract class ThemableBrowserActivity : AppCompatActivity() { // TODO reduce protected visibility @Inject protected lateinit var userPreferences: UserPreferences - private var themeId: Int = 0 + private var themeId: AppTheme = AppTheme.LIGHT private var showTabsInDrawer: Boolean = false private var shouldRunOnResumeActions = false + /** + * Override this to provide an alternate theme that should be set for every instance of this + * activity regardless of the user's preference. + */ + @StyleRes + protected open fun provideThemeOverride(): Int? = null + override fun onCreate(savedInstanceState: Bundle?) { injector.inject(this) themeId = userPreferences.useTheme showTabsInDrawer = userPreferences.showTabsInDrawer // set the theme - if (themeId == 1) { - setTheme(R.style.Theme_DarkTheme) - } else if (themeId == 2) { - setTheme(R.style.Theme_BlackTheme) - } + setTheme(provideThemeOverride() ?: when (userPreferences.useTheme) { + AppTheme.LIGHT -> R.style.Theme_LightTheme + AppTheme.DARK -> R.style.Theme_DarkTheme + AppTheme.BLACK -> R.style.Theme_BlackTheme + }) super.onCreate(savedInstanceState) resetPreferences() @@ -53,7 +62,7 @@ abstract class ThemableBrowserActivity : AppCompatActivity() { private fun resetPreferences() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (userPreferences.useBlackStatusBar) { + if (userPreferences.useBlackStatusBar || !userPreferences.showTabsInDrawer) { window.statusBarColor = Color.BLACK } else { window.statusBarColor = ThemeUtils.getStatusBarColor(this) @@ -81,9 +90,8 @@ abstract class ThemableBrowserActivity : AppCompatActivity() { super.onResume() resetPreferences() shouldRunOnResumeActions = true - val themePreference = userPreferences.useTheme val drawerTabs = userPreferences.showTabsInDrawer - if (themeId != themePreference || showTabsInDrawer != drawerTabs) { + if (themeId != userPreferences.useTheme || showTabsInDrawer != drawerTabs) { restart() } } diff --git a/app/src/main/java/acr/browser/lightning/browser/bookmarks/BookmarksDrawerView.kt b/app/src/main/java/acr/browser/lightning/browser/bookmarks/BookmarksDrawerView.kt index 789ce16e2..ac14cbfc7 100644 --- a/app/src/main/java/acr/browser/lightning/browser/bookmarks/BookmarksDrawerView.kt +++ b/app/src/main/java/acr/browser/lightning/browser/bookmarks/BookmarksDrawerView.kt @@ -37,7 +37,6 @@ import io.reactivex.Scheduler import io.reactivex.Single import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.subscribeBy -import kotlinx.android.synthetic.main.bookmark_drawer.view.* import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject @@ -71,25 +70,32 @@ class BookmarksDrawerView @JvmOverloads constructor( private val uiModel = BookmarkUiModel() + private var bookmarkRecyclerView: RecyclerView? = null + private var backNavigationView: ImageView? = null + private var addBookmarkView: ImageView? = null + init { context.inflater.inflate(R.layout.bookmark_drawer, this, true) context.injector.inject(this) uiController = context as UIController - bookmark_back_button.setOnClickListener { + bookmarkRecyclerView = findViewById(R.id.bookmark_list_view) + backNavigationView = findViewById(R.id.bookmark_back_button) + addBookmarkView = findViewById(R.id.action_add_bookmark) + backNavigationView?.setOnClickListener { if (!uiModel.isCurrentFolderRoot()) { setBookmarksShown(null, true) - bookmark_list_view.layoutManager?.scrollToPosition(scrollIndex) + bookmarkRecyclerView?.layoutManager?.scrollToPosition(scrollIndex) } } - action_add_bookmark.setOnClickListener { uiController.bookmarkButtonClicked() } - action_reading.setOnClickListener { + addBookmarkView?.setOnClickListener { uiController.bookmarkButtonClicked() } + findViewById(R.id.action_reading).setOnClickListener { getTabsManager().currentTab?.url?.let { ReadingActivity.launch(context, it) } } - action_page_tools.setOnClickListener { showPageToolsDialog(context) } + findViewById(R.id.action_page_tools).setOnClickListener { showPageToolsDialog(context) } bookmarkAdapter = BookmarkListAdapter( context, @@ -100,7 +106,7 @@ class BookmarksDrawerView @JvmOverloads constructor( ::handleItemClick ) - bookmark_list_view.let { + bookmarkRecyclerView?.let { it.layoutManager = LinearLayoutManager(context) it.adapter = bookmarkAdapter } @@ -126,9 +132,8 @@ class BookmarksDrawerView @JvmOverloads constructor( .observeOn(mainScheduler) .subscribe { isBookmark -> bookmarkUpdateSubscription = null - action_add_bookmark_image.isSelected = isBookmark - action_add_bookmark.isEnabled = !url.isSpecialUrl() - action_add_bookmark_image.isEnabled = !url.isSpecialUrl() + addBookmarkView?.isSelected = isBookmark + addBookmarkView?.isEnabled = !url.isSpecialUrl() } } @@ -166,12 +171,12 @@ class BookmarksDrawerView @JvmOverloads constructor( } if (animate) { - bookmark_back_button_image?.let { + backNavigationView?.let { val transition = AnimationUtils.createRotationTransitionAnimation(it, resource) it.startAnimation(transition) } } else { - bookmark_back_button_image?.setImageResource(resource) + backNavigationView?.setImageResource(resource) } } @@ -187,7 +192,7 @@ class BookmarksDrawerView @JvmOverloads constructor( private fun handleItemClick(bookmark: Bookmark) = when (bookmark) { is Bookmark.Folder -> { - scrollIndex = (bookmark_list_view.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() + scrollIndex = (bookmarkRecyclerView?.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() setBookmarksShown(bookmark.title, true) } is Bookmark.Entry -> uiController.bookmarkItemClicked(bookmark) @@ -238,7 +243,7 @@ class BookmarksDrawerView @JvmOverloads constructor( uiController.onBackButtonPressed() } else { setBookmarksShown(null, true) - bookmark_list_view?.layoutManager?.scrollToPosition(scrollIndex) + bookmarkRecyclerView?.layoutManager?.scrollToPosition(scrollIndex) } } diff --git a/app/src/main/java/acr/browser/lightning/browser/tabs/TabViewHolder.kt b/app/src/main/java/acr/browser/lightning/browser/tabs/TabViewHolder.kt index 18fa9d111..c3cf3caed 100644 --- a/app/src/main/java/acr/browser/lightning/browser/tabs/TabViewHolder.kt +++ b/app/src/main/java/acr/browser/lightning/browser/tabs/TabViewHolder.kt @@ -3,7 +3,6 @@ package acr.browser.lightning.browser.tabs import acr.browser.lightning.R import acr.browser.lightning.controller.UIController import android.view.View -import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView @@ -19,7 +18,7 @@ class TabViewHolder( val txtTitle: TextView = view.findViewById(R.id.textTab) val favicon: ImageView = view.findViewById(R.id.faviconTab) - val exitButton: FrameLayout = view.findViewById(R.id.deleteAction) + val exitButton: View = view.findViewById(R.id.deleteAction) val layout: LinearLayout = view.findViewById(R.id.tab_item_background) init { diff --git a/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDesktopAdapter.kt b/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDesktopAdapter.kt index 81fcfc6e1..905828189 100644 --- a/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDesktopAdapter.kt +++ b/app/src/main/java/acr/browser/lightning/browser/tabs/TabsDesktopAdapter.kt @@ -2,9 +2,7 @@ package acr.browser.lightning.browser.tabs import acr.browser.lightning.R import acr.browser.lightning.controller.UIController -import acr.browser.lightning.extensions.desaturate -import acr.browser.lightning.extensions.drawTrapezoid -import acr.browser.lightning.extensions.inflater +import acr.browser.lightning.extensions.* import acr.browser.lightning.utils.ThemeUtils import acr.browser.lightning.utils.Utils import android.content.Context @@ -12,7 +10,6 @@ import android.content.res.Resources import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color -import android.graphics.PorterDuff import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.view.ViewGroup @@ -35,13 +32,21 @@ class TabsDesktopAdapter( init { val backgroundColor = Utils.mixTwoColors(ThemeUtils.getPrimaryColor(context), Color.BLACK, 0.75f) - val backgroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175f), Utils.dpToPx(30f), Bitmap.Config.ARGB_8888).also { + 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(resources, backgroundTabBitmap) val foregroundColor = ThemeUtils.getPrimaryColor(context) - foregroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175f), Utils.dpToPx(30f), Bitmap.Config.ARGB_8888).also { + 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) } } @@ -82,7 +87,7 @@ class TabsDesktopAdapter( if (isForeground) { val foregroundDrawable = BitmapDrawable(resources, foregroundTabBitmap) if (uiController.isColorMode()) { - foregroundDrawable.setColorFilter(uiController.getUiColor(), PorterDuff.Mode.SRC_IN) + foregroundDrawable.tint(uiController.getUiColor()) } TextViewCompat.setTextAppearance(viewHolder.txtTitle, R.style.boldText) viewHolder.layout.background = foregroundDrawable 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 bf4a03029..b648ea775 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 @@ -2,9 +2,9 @@ package acr.browser.lightning.browser.tabs import acr.browser.lightning.R import acr.browser.lightning.browser.TabsView -import acr.browser.lightning.list.VerticalItemAnimator import acr.browser.lightning.controller.UIController import acr.browser.lightning.extensions.inflater +import acr.browser.lightning.list.VerticalItemAnimator import acr.browser.lightning.view.LightningView import android.content.Context import android.util.AttributeSet @@ -25,10 +25,14 @@ class TabsDrawerView @JvmOverloads constructor( private val uiController = context as UIController private val tabsAdapter = TabsDrawerAdapter(uiController) private val tabList: RecyclerView + private val actionBack: View + private val actionForward: View init { orientation = VERTICAL context.inflater.inflate(R.layout.tab_drawer, this, true) + actionBack = findViewById(R.id.action_back) + actionForward = findViewById(R.id.action_forward) val animator = VerticalItemAnimator().apply { supportsChangeAnimations = false @@ -92,13 +96,11 @@ class TabsDrawerView @JvmOverloads constructor( } override fun setGoBackEnabled(isEnabled: Boolean) { - findViewById(R.id.action_back).isEnabled = isEnabled - findViewById(R.id.icon_back).isEnabled = isEnabled + actionBack.isEnabled = isEnabled } override fun setGoForwardEnabled(isEnabled: Boolean) { - findViewById(R.id.action_forward).isEnabled = isEnabled - findViewById(R.id.icon_forward).isEnabled = isEnabled + actionForward.isEnabled = isEnabled } } diff --git a/app/src/main/java/acr/browser/lightning/constant/Constants.kt b/app/src/main/java/acr/browser/lightning/constant/Constants.kt index ea22f1466..76d7f8f9f 100644 --- a/app/src/main/java/acr/browser/lightning/constant/Constants.kt +++ b/app/src/main/java/acr/browser/lightning/constant/Constants.kt @@ -6,8 +6,8 @@ package acr.browser.lightning.constant // Hardcoded user agents -const val DESKTOP_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36" -const val MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; U; Android 4.4; en-us; Nexus 4 Build/JOP24G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30" +const val DESKTOP_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" +const val MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; Android 10; Pixel Build/QP1A.190711.019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36" // URL Schemes const val HTTP = "http://" @@ -21,13 +21,6 @@ const val SCHEME_HOMEPAGE = "${ABOUT}home" const val SCHEME_BLANK = "${ABOUT}blank" const val SCHEME_BOOKMARKS = "${ABOUT}bookmarks" -const val LOAD_READING_URL = "ReadingUrl" - -const val NO_PROXY = 0 -const val PROXY_ORBOT = 1 -const val PROXY_I2P = 2 -const val PROXY_MANUAL = 3 - const val UTF8 = "UTF-8" // Default text encoding we will use diff --git a/app/src/main/java/acr/browser/lightning/constant/Proxy.kt b/app/src/main/java/acr/browser/lightning/constant/Proxy.kt deleted file mode 100644 index f125128a3..000000000 --- a/app/src/main/java/acr/browser/lightning/constant/Proxy.kt +++ /dev/null @@ -1,13 +0,0 @@ -package acr.browser.lightning.constant - -import androidx.annotation.IntDef - - -/** - * Proxy choice integer definition. - * - * These should match the order of @array/proxy_choices_array - */ -@IntDef(NO_PROXY, PROXY_ORBOT, PROXY_I2P, PROXY_MANUAL) -@Retention(AnnotationRetention.SOURCE) -annotation class Proxy diff --git a/app/src/main/java/acr/browser/lightning/controller/UIController.kt b/app/src/main/java/acr/browser/lightning/controller/UIController.kt index f1dfe8f08..10a7817d1 100644 --- a/app/src/main/java/acr/browser/lightning/controller/UIController.kt +++ b/app/src/main/java/acr/browser/lightning/controller/UIController.kt @@ -7,6 +7,7 @@ import acr.browser.lightning.browser.TabsManager import acr.browser.lightning.database.Bookmark import acr.browser.lightning.dialog.LightningDialogBuilder import acr.browser.lightning.view.LightningView +import android.content.pm.ActivityInfo import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.net.Uri @@ -83,12 +84,11 @@ interface UIController { /** * Notify the controller that it should display the custom [view] in full-screen to the user. */ - fun onShowCustomView(view: View, callback: CustomViewCallback) - - /** - * Notify the controller that it should display the custom [view] in full-screen to the user. - */ - fun onShowCustomView(view: View, callback: CustomViewCallback, requestedOrientation: Int) + fun onShowCustomView( + view: View, + callback: CustomViewCallback, + requestedOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + ) /** * Notify the controller that it should hide the custom view which was previously displayed in diff --git a/app/src/main/java/acr/browser/lightning/database/downloads/DownloadEntry.kt b/app/src/main/java/acr/browser/lightning/database/downloads/DownloadEntry.kt index 9febf70c6..3e25b5f5a 100644 --- a/app/src/main/java/acr/browser/lightning/database/downloads/DownloadEntry.kt +++ b/app/src/main/java/acr/browser/lightning/database/downloads/DownloadEntry.kt @@ -2,6 +2,10 @@ package acr.browser.lightning.database.downloads /** * An entry in the downloads database. + * + * @param url The URL of the original download. + * @param title The file name. + * @param contentSize The user readable content size. */ data class DownloadEntry( val url: String, diff --git a/app/src/main/java/acr/browser/lightning/device/ScreenSize.kt b/app/src/main/java/acr/browser/lightning/device/ScreenSize.kt index 9eb9a3105..4b93a17e0 100644 --- a/app/src/main/java/acr/browser/lightning/device/ScreenSize.kt +++ b/app/src/main/java/acr/browser/lightning/device/ScreenSize.kt @@ -2,6 +2,7 @@ package acr.browser.lightning.device import android.content.Context import android.content.res.Configuration +import dagger.Reusable import javax.inject.Inject /** @@ -9,6 +10,7 @@ import javax.inject.Inject * * Created by anthonycr on 2/19/18. */ +@Reusable class ScreenSize @Inject constructor(private val context: Context) { fun isTablet(): Boolean = 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 676323ba1..eee6e18fa 100644 --- a/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt +++ b/app/src/main/java/acr/browser/lightning/di/AppBindsModule.kt @@ -25,32 +25,32 @@ import dagger.Module * Dependency injection module used to bind implementations to interfaces. */ @Module -abstract class AppBindsModule { +interface AppBindsModule { @Binds - abstract fun provideBookmarkModel(bookmarkDatabase: BookmarkDatabase): BookmarkRepository + fun provideBookmarkModel(bookmarkDatabase: BookmarkDatabase): BookmarkRepository @Binds - abstract fun provideDownloadsModel(downloadsDatabase: DownloadsDatabase): DownloadsRepository + fun provideDownloadsModel(downloadsDatabase: DownloadsDatabase): DownloadsRepository @Binds - abstract fun providesHistoryModel(historyDatabase: HistoryDatabase): HistoryRepository + fun providesHistoryModel(historyDatabase: HistoryDatabase): HistoryRepository @Binds - abstract fun providesAdBlockAllowListModel(adBlockAllowListDatabase: AdBlockAllowListDatabase): AdBlockAllowListRepository + fun providesAdBlockAllowListModel(adBlockAllowListDatabase: AdBlockAllowListDatabase): AdBlockAllowListRepository @Binds - abstract fun providesAllowListModel(sessionAllowListModel: SessionAllowListModel): AllowListModel + fun providesAllowListModel(sessionAllowListModel: SessionAllowListModel): AllowListModel @Binds - abstract fun providesSslWarningPreferences(sessionSslWarningPreferences: SessionSslWarningPreferences): SslWarningPreferences + fun providesSslWarningPreferences(sessionSslWarningPreferences: SessionSslWarningPreferences): SslWarningPreferences @Binds - abstract fun providesHostsDataSource(assetsHostsDataSource: AssetsHostsDataSource): HostsDataSource + fun providesHostsDataSource(assetsHostsDataSource: AssetsHostsDataSource): HostsDataSource @Binds - abstract fun providesHostsRepository(hostsDatabase: HostsDatabase): HostsRepository + fun providesHostsRepository(hostsDatabase: HostsDatabase): HostsRepository @Binds - abstract fun providesHostsDataSourceProvider(preferencesHostsDataSourceProvider: PreferencesHostsDataSourceProvider): HostsDataSourceProvider + fun providesHostsDataSourceProvider(preferencesHostsDataSourceProvider: PreferencesHostsDataSourceProvider): HostsDataSourceProvider } diff --git a/app/src/main/java/acr/browser/lightning/di/AppModule.kt b/app/src/main/java/acr/browser/lightning/di/AppModule.kt index 028540b31..da885839c 100644 --- a/app/src/main/java/acr/browser/lightning/di/AppModule.kt +++ b/app/src/main/java/acr/browser/lightning/di/AppModule.kt @@ -5,6 +5,9 @@ import acr.browser.lightning.device.BuildType import acr.browser.lightning.html.ListPageReader import acr.browser.lightning.html.bookmark.BookmarkPageReader import acr.browser.lightning.html.homepage.HomePageReader +import acr.browser.lightning.js.InvertPage +import acr.browser.lightning.js.TextReflow +import acr.browser.lightning.js.ThemeColor import acr.browser.lightning.log.AndroidLogger import acr.browser.lightning.log.Logger import acr.browser.lightning.log.NoOpLogger @@ -133,13 +136,11 @@ class AppModule { fun providesSuggestionsHttpClient(application: Application): Single = Single.fromCallable { val intervalDay = TimeUnit.DAYS.toSeconds(1) - val rewriteCacheControlInterceptor = object : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val originalResponse = chain.proceed(chain.request()) - return originalResponse.newBuilder() - .header("cache-control", "max-age=$intervalDay, max-stale=$intervalDay") - .build() - } + val rewriteCacheControlInterceptor = Interceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse.newBuilder() + .header("cache-control", "max-age=$intervalDay, max-stale=$intervalDay") + .build() } val suggestionsCache = File(application.cacheDir, "suggestion_responses") @@ -156,13 +157,11 @@ class AppModule { fun providesHostsHttpClient(application: Application): Single = Single.fromCallable { val intervalDay = TimeUnit.DAYS.toSeconds(365) - val rewriteCacheControlInterceptor = object : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val originalResponse = chain.proceed(chain.request()) - return originalResponse.newBuilder() - .header("cache-control", "max-age=$intervalDay, max-stale=$intervalDay") - .build() - } + val rewriteCacheControlInterceptor = Interceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse.newBuilder() + .header("cache-control", "max-age=$intervalDay, max-stale=$intervalDay") + .build() } val suggestionsCache = File(application.cacheDir, "hosts_cache") @@ -194,6 +193,15 @@ class AppModule { @Provides fun providesBookmarkPageReader(): BookmarkPageReader = MezzanineGenerator.BookmarkPageReader() + @Provides + fun providesTextReflow(): TextReflow = MezzanineGenerator.TextReflow() + + @Provides + fun providesThemeColor(): ThemeColor = MezzanineGenerator.ThemeColor() + + @Provides + fun providesInvertPage(): InvertPage = MezzanineGenerator.InvertPage() + } @Qualifier diff --git a/app/src/main/java/acr/browser/lightning/dialog/BrowserDialog.kt b/app/src/main/java/acr/browser/lightning/dialog/BrowserDialog.kt index 73ca466e4..608a1b07f 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/BrowserDialog.kt +++ b/app/src/main/java/acr/browser/lightning/dialog/BrowserDialog.kt @@ -18,6 +18,7 @@ package acr.browser.lightning.dialog import acr.browser.lightning.R import acr.browser.lightning.extensions.dimen import acr.browser.lightning.extensions.inflater +import acr.browser.lightning.extensions.resizeAndShow import acr.browser.lightning.list.RecyclerViewDialogItemAdapter import acr.browser.lightning.list.RecyclerViewStringAdapter import acr.browser.lightning.utils.DeviceUtils @@ -66,9 +67,7 @@ object BrowserDialog { builder.setView(layout) - val dialog = builder.show() - - setDialogSize(context, dialog) + val dialog = builder.resizeAndShow() adapter.onItemClickListener = { item -> item.onClick() @@ -83,7 +82,7 @@ object BrowserDialog { * the dialog. */ fun showListChoices(activity: Activity, @StringRes title: Int, vararg items: DialogItem) { - val dialog = AlertDialog.Builder(activity).apply { + AlertDialog.Builder(activity).apply { setTitle(title) val choices = items.map { activity.getString(it.title) }.toTypedArray() @@ -93,9 +92,7 @@ object BrowserDialog { items[which].onClick() } setPositiveButton(activity.getString(R.string.action_ok), null) - }.show() - - setDialogSize(activity, dialog) + }.resizeAndShow() } @JvmStatic @@ -123,9 +120,7 @@ object BrowserDialog { builder.setView(layout) - val dialog = builder.show() - - setDialogSize(activity, dialog) + val dialog = builder.resizeAndShow() adapter.onItemClickListener = { item -> item.onClick() @@ -148,15 +143,13 @@ object BrowserDialog { } else { activity.getString(message) } - val dialog = AlertDialog.Builder(activity).apply { + AlertDialog.Builder(activity).apply { setTitle(title) setMessage(messageValue) setOnCancelListener { onCancel() } setPositiveButton(positiveButton.title) { _, _ -> positiveButton.onClick() } setNegativeButton(negativeButton.title) { _, _ -> negativeButton.onClick() } - }.show() - - setDialogSize(activity, dialog) + }.resizeAndShow() } @JvmStatic @@ -185,14 +178,12 @@ object BrowserDialog { editText.setText(currentText) } - val editorDialog = AlertDialog.Builder(activity) + AlertDialog.Builder(activity) .setTitle(title) .setView(dialogView) .setPositiveButton(action ) { _, _ -> textInputListener(editText.text.toString()) } - - val dialog = editorDialog.show() - setDialogSize(activity, dialog) + .resizeAndShow() } @JvmStatic @@ -213,8 +204,7 @@ object BrowserDialog { activity?.let { AlertDialog.Builder(activity).apply { block(it) - val dialog = show() - setDialogSize(it, dialog) + resizeAndShow() } } } 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 7613a9ba1..997009258 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt +++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.kt @@ -13,6 +13,7 @@ import acr.browser.lightning.di.DatabaseScheduler import acr.browser.lightning.di.MainScheduler import acr.browser.lightning.download.DownloadHandler import acr.browser.lightning.extensions.copyToClipboard +import acr.browser.lightning.extensions.resizeAndShow import acr.browser.lightning.extensions.toast import acr.browser.lightning.html.bookmark.BookmarkPageFactory import acr.browser.lightning.preference.UserPreferences @@ -26,6 +27,7 @@ import android.widget.AutoCompleteTextView import android.widget.EditText import androidx.appcompat.app.AlertDialog import androidx.core.net.toUri +import dagger.Reusable import io.reactivex.Scheduler import io.reactivex.rxkotlin.subscribeBy import javax.inject.Inject @@ -33,6 +35,7 @@ import javax.inject.Inject /** * A builder of various dialogs. */ +@Reusable class LightningDialogBuilder @Inject constructor( private val bookmarkManager: BookmarkRepository, private val downloadsModel: DownloadsRepository, @@ -181,8 +184,7 @@ class LightningDialogBuilder @Inject constructor( ) } editBookmarkDialog.setNegativeButton(R.string.action_cancel) { _, _ -> } - val dialog = editBookmarkDialog.show() - BrowserDialog.setDialogSize(activity, dialog) + editBookmarkDialog.resizeAndShow() } } @@ -223,8 +225,7 @@ class LightningDialogBuilder @Inject constructor( .observeOn(mainScheduler) .subscribe(uiController::handleBookmarksChange) } - val dialog = editBookmarkDialog.show() - BrowserDialog.setDialogSize(activity, dialog) + editBookmarkDialog.resizeAndShow() } } 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 253b314fb..30ae4c584 100644 --- a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java +++ b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java @@ -140,7 +140,7 @@ private static String encodePath(@NonNull String path) { return path; } - StringBuilder sb = new StringBuilder(""); + StringBuilder sb = new StringBuilder(); for (char c : chars) { if (c == '[' || c == ']' || c == '|') { sb.append('%'); diff --git a/app/src/main/java/acr/browser/lightning/extensions/AlertDialogExtensions.kt b/app/src/main/java/acr/browser/lightning/extensions/AlertDialogExtensions.kt new file mode 100644 index 000000000..0031ce49f --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/extensions/AlertDialogExtensions.kt @@ -0,0 +1,30 @@ +package acr.browser.lightning.extensions + +import acr.browser.lightning.dialog.BrowserDialog +import android.app.Dialog +import androidx.appcompat.app.AlertDialog + +/** + * Show single choice items. + * + * @param items A list of items and their user readable string description. + * @param checkedItem The item that will be checked when the dialog is displayed. + * @param onClick Called when an item is clicked. The item clicked is provided. + */ +fun AlertDialog.Builder.withSingleChoiceItems( + items: List>, + checkedItem: T, + onClick: (T) -> Unit +) { + val checkedIndex = items.map(Pair::first).indexOf(checkedItem) + val titles = items.map(Pair::second).toTypedArray() + setSingleChoiceItems(titles, checkedIndex) { _, which -> + onClick(items[which].first) + } +} + +/** + * Ensures that the dialog is appropriately sized and displays it. + */ +@Suppress("NOTHING_TO_INLINE") +inline fun AlertDialog.Builder.resizeAndShow(): Dialog = show().also { BrowserDialog.setDialogSize(context, it) } diff --git a/app/src/main/java/acr/browser/lightning/extensions/ClipboardManagerExtensions.kt b/app/src/main/java/acr/browser/lightning/extensions/ClipboardManagerExtensions.kt index 1b9a6c28f..5fb607186 100644 --- a/app/src/main/java/acr/browser/lightning/extensions/ClipboardManagerExtensions.kt +++ b/app/src/main/java/acr/browser/lightning/extensions/ClipboardManagerExtensions.kt @@ -7,5 +7,5 @@ import android.content.ClipboardManager * Copies the [text] to the clipboard with the label `URL`. */ fun ClipboardManager.copyToClipboard(text: String) { - primaryClip = ClipData.newPlainText("URL", text) + setPrimaryClip(ClipData.newPlainText("URL", text)) } diff --git a/app/src/main/java/acr/browser/lightning/extensions/DialogExtensions.kt b/app/src/main/java/acr/browser/lightning/extensions/DialogExtensions.kt deleted file mode 100644 index 94d218f1d..000000000 --- a/app/src/main/java/acr/browser/lightning/extensions/DialogExtensions.kt +++ /dev/null @@ -1,11 +0,0 @@ -@file:Suppress("NOTHING_TO_INLINE") - -package acr.browser.lightning.extensions - -import acr.browser.lightning.dialog.BrowserDialog -import androidx.appcompat.app.AlertDialog - -/** - * Ensures that the dialog is appropriately sized and displays it. - */ -inline fun AlertDialog.Builder.resizeAndShow() = BrowserDialog.setDialogSize(context, this.show()) diff --git a/app/src/main/java/acr/browser/lightning/extensions/DrawableExtensions.kt b/app/src/main/java/acr/browser/lightning/extensions/DrawableExtensions.kt new file mode 100644 index 000000000..dcf955436 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/extensions/DrawableExtensions.kt @@ -0,0 +1,13 @@ +package acr.browser.lightning.extensions + +import android.graphics.drawable.Drawable +import androidx.annotation.ColorInt +import androidx.core.graphics.BlendModeColorFilterCompat +import androidx.core.graphics.BlendModeCompat + +/** + * Tint a drawable with the provided [color], using [BlendModeCompat.SRC_IN]. + */ +fun Drawable.tint(@ColorInt color: Int) { + colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_IN) +} diff --git a/app/src/main/java/acr/browser/lightning/favicon/FaviconModel.kt b/app/src/main/java/acr/browser/lightning/favicon/FaviconModel.kt index c150b1ddf..591a6c68b 100644 --- a/app/src/main/java/acr/browser/lightning/favicon/FaviconModel.kt +++ b/app/src/main/java/acr/browser/lightning/favicon/FaviconModel.kt @@ -31,7 +31,7 @@ class FaviconModel @Inject constructor( ) { private val loaderOptions = BitmapFactory.Options() - private val bookmarkIconSize = application.resources.getDimensionPixelSize(R.dimen.bookmark_item_icon_size) + private val bookmarkIconSize = application.resources.getDimensionPixelSize(R.dimen.material_grid_small_icon) private val faviconCache = object : LruCache(FileUtils.megabytesToBytes(1).toInt()) { override fun sizeOf(key: String, value: Bitmap) = value.byteCount } 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 aeb5f27e4..e4b01d31d 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 @@ -15,6 +15,7 @@ import acr.browser.lightning.utils.ThemeUtils 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 java.io.File @@ -25,6 +26,7 @@ import javax.inject.Inject /** * Created by anthonycr on 9/23/18. */ +@Reusable class BookmarkPageFactory @Inject constructor( private val application: Application, private val bookmarkModel: BookmarkRepository, 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 1a3b99ada..f2f075e8b 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 @@ -9,6 +9,7 @@ import acr.browser.lightning.html.ListPageReader import acr.browser.lightning.html.jsoup.* import acr.browser.lightning.preference.UserPreferences import android.app.Application +import dagger.Reusable import io.reactivex.Single import java.io.File import java.io.FileWriter @@ -17,6 +18,7 @@ import javax.inject.Inject /** * The factory for the downloads page. */ +@Reusable class DownloadPageFactory @Inject constructor( private val application: Application, private val userPreferences: UserPreferences, 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 f1a0169ac..197509fdd 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 @@ -7,6 +7,7 @@ import acr.browser.lightning.html.HtmlPageFactory import acr.browser.lightning.html.ListPageReader import acr.browser.lightning.html.jsoup.* import android.app.Application +import dagger.Reusable import io.reactivex.Completable import io.reactivex.Single import java.io.File @@ -16,6 +17,7 @@ import javax.inject.Inject /** * Factory for the history page. */ +@Reusable class HistoryPageFactory @Inject constructor( private val listPageReader: ListPageReader, private val application: Application, diff --git a/app/src/main/java/acr/browser/lightning/html/homepage/HomePageFactory.kt b/app/src/main/java/acr/browser/lightning/html/homepage/HomePageFactory.kt index a3866c936..196a745b1 100644 --- a/app/src/main/java/acr/browser/lightning/html/homepage/HomePageFactory.kt +++ b/app/src/main/java/acr/browser/lightning/html/homepage/HomePageFactory.kt @@ -7,6 +7,7 @@ import acr.browser.lightning.html.HtmlPageFactory import acr.browser.lightning.html.jsoup.* import acr.browser.lightning.search.SearchEngineProvider import android.app.Application +import dagger.Reusable import io.reactivex.Single import java.io.File import java.io.FileWriter @@ -15,6 +16,7 @@ import javax.inject.Inject /** * A factory for the home page. */ +@Reusable class HomePageFactory @Inject constructor( private val application: Application, private val searchEngineProvider: SearchEngineProvider, diff --git a/app/src/main/java/acr/browser/lightning/log/AndroidLogger.kt b/app/src/main/java/acr/browser/lightning/log/AndroidLogger.kt index fbfbb71b0..c2dd63f8f 100644 --- a/app/src/main/java/acr/browser/lightning/log/AndroidLogger.kt +++ b/app/src/main/java/acr/browser/lightning/log/AndroidLogger.kt @@ -1,11 +1,13 @@ package acr.browser.lightning.log import android.util.Log +import dagger.Reusable import javax.inject.Inject /** * A logger that utilizes the [Log] class. */ +@Reusable class AndroidLogger @Inject constructor() : Logger { override fun log(tag: String, message: String) { diff --git a/app/src/main/java/acr/browser/lightning/log/NoOpLogger.kt b/app/src/main/java/acr/browser/lightning/log/NoOpLogger.kt index 641158022..2a3e132f5 100644 --- a/app/src/main/java/acr/browser/lightning/log/NoOpLogger.kt +++ b/app/src/main/java/acr/browser/lightning/log/NoOpLogger.kt @@ -1,10 +1,12 @@ package acr.browser.lightning.log +import dagger.Reusable import javax.inject.Inject /** * A logger that doesn't log. */ +@Reusable class NoOpLogger @Inject constructor() : Logger { override fun log(tag: String, message: String) = Unit diff --git a/app/src/main/java/acr/browser/lightning/network/NetworkConnectivityModel.kt b/app/src/main/java/acr/browser/lightning/network/NetworkConnectivityModel.kt index 25a718337..bd052d374 100644 --- a/app/src/main/java/acr/browser/lightning/network/NetworkConnectivityModel.kt +++ b/app/src/main/java/acr/browser/lightning/network/NetworkConnectivityModel.kt @@ -3,6 +3,7 @@ package acr.browser.lightning.network import acr.browser.lightning.rx.BroadcastReceiverObservable import android.app.Application import android.net.ConnectivityManager +import dagger.Reusable import io.reactivex.Observable import javax.inject.Inject @@ -10,6 +11,7 @@ import javax.inject.Inject /** * A model that supplies network connectivity status updates. */ +@Reusable class NetworkConnectivityModel @Inject constructor( private val connectivityManager: ConnectivityManager, private val application: Application diff --git a/app/src/main/java/acr/browser/lightning/preference/IntEnum.kt b/app/src/main/java/acr/browser/lightning/preference/IntEnum.kt new file mode 100644 index 000000000..11b41ff0f --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/preference/IntEnum.kt @@ -0,0 +1,12 @@ +package acr.browser.lightning.preference + +/** + * An enum value that has an integer representation. + */ +interface IntEnum { + + /** + * The integer representation of the enum value. + */ + val value: Int +} diff --git a/app/src/main/java/acr/browser/lightning/preference/UserPreferences.kt b/app/src/main/java/acr/browser/lightning/preference/UserPreferences.kt index 5d99bafbe..eacc21956 100644 --- a/app/src/main/java/acr/browser/lightning/preference/UserPreferences.kt +++ b/app/src/main/java/acr/browser/lightning/preference/UserPreferences.kt @@ -1,18 +1,18 @@ package acr.browser.lightning.preference +import acr.browser.lightning.AppTheme +import acr.browser.lightning.browser.ProxyChoice +import acr.browser.lightning.browser.SearchBoxDisplayChoice import acr.browser.lightning.browser.SearchBoxModel import acr.browser.lightning.constant.DEFAULT_ENCODING -import acr.browser.lightning.constant.NO_PROXY import acr.browser.lightning.constant.SCHEME_HOMEPAGE import acr.browser.lightning.device.ScreenSize import acr.browser.lightning.di.UserPrefs -import acr.browser.lightning.preference.delegates.booleanPreference -import acr.browser.lightning.preference.delegates.intPreference -import acr.browser.lightning.preference.delegates.nullableStringPreference -import acr.browser.lightning.preference.delegates.stringPreference +import acr.browser.lightning.preference.delegates.* import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.search.engine.GoogleSearch import acr.browser.lightning.utils.FileUtils +import acr.browser.lightning.view.RenderingMode import android.content.SharedPreferences import javax.inject.Inject import javax.inject.Singleton @@ -167,7 +167,7 @@ class UserPreferences @Inject constructor( /** * The index of the rendering mode that should be used by the browser. */ - var renderingMode by preferences.intPreference(RENDERING_MODE, 0) + var renderingMode by preferences.enumPreference(RENDERING_MODE, RenderingMode.NORMAL) /** * True if third party cookies should be disallowed by the browser, false if they should be @@ -186,7 +186,7 @@ class UserPreferences @Inject constructor( * * @see SearchBoxModel */ - var urlBoxContentChoice by preferences.intPreference(URL_BOX_CONTENTS, 0) + var urlBoxContentChoice by preferences.enumPreference(URL_BOX_CONTENTS, SearchBoxDisplayChoice.DOMAIN) /** * True if the browser should invert the display colors of the web page content, false @@ -202,7 +202,7 @@ class UserPreferences @Inject constructor( /** * The index of the theme used by the application. */ - var useTheme by preferences.intPreference(THEME, 0) + var useTheme by preferences.enumPreference(THEME, AppTheme.LIGHT) /** * The text encoding used by the browser. @@ -253,15 +253,15 @@ class UserPreferences @Inject constructor( /** * The index of the proxy choice. */ - var proxyChoice by preferences.intPreference(PROXY_CHOICE, NO_PROXY) + var proxyChoice by preferences.enumPreference(PROXY_CHOICE, ProxyChoice.NONE) /** - * The proxy host used depending on the [proxyChoice]. + * The proxy host used when [proxyChoice] is [ProxyChoice.MANUAL]. */ var proxyHost by preferences.stringPreference(USE_PROXY_HOST, "localhost") /** - * The proxy port used depending on the [proxyChoice]. + * The proxy port used when [proxyChoice] is [ProxyChoice.MANUAL]. */ var proxyPort by preferences.intPreference(USE_PROXY_PORT, 8118) diff --git a/app/src/main/java/acr/browser/lightning/preference/delegates/EnumPreference.kt b/app/src/main/java/acr/browser/lightning/preference/delegates/EnumPreference.kt new file mode 100644 index 000000000..7c0341dd2 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/preference/delegates/EnumPreference.kt @@ -0,0 +1,41 @@ +package acr.browser.lightning.preference.delegates + +import acr.browser.lightning.preference.IntEnum +import android.content.SharedPreferences +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * An [Enum] delegate that is backed by [SharedPreferences]. + */ +class EnumPreference( + name: String, + private val defaultValue: T, + private val clazz: Class, + preferences: SharedPreferences +) : ReadWriteProperty where T : Enum, T : IntEnum { + + private var backingInt: Int by preferences.intPreference(name, defaultValue.value) + + override fun getValue(thisRef: Any, property: KProperty<*>): T { + return clazz.enumConstants!!.first { it.value == backingInt } ?: defaultValue + } + + override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { + backingInt = value.value + } + +} + +/** + * Creates a [T] enum from [SharedPreferences] with the provide arguments. + */ +inline fun SharedPreferences.enumPreference( + name: String, + defaultValue: T +): ReadWriteProperty where T : Enum, T : IntEnum = EnumPreference( + name, + defaultValue, + T::class.java, + this +) diff --git a/app/src/main/java/acr/browser/lightning/reading/ArticleTextExtractor.java b/app/src/main/java/acr/browser/lightning/reading/ArticleTextExtractor.java index 111c95119..e58f6c957 100644 --- a/app/src/main/java/acr/browser/lightning/reading/ArticleTextExtractor.java +++ b/app/src/main/java/acr/browser/lightning/reading/ArticleTextExtractor.java @@ -1,8 +1,5 @@ package acr.browser.lightning.reading; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -23,6 +20,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * This class is thread safe. * Class for content extraction from string form of webpage @@ -42,9 +42,9 @@ public class ArticleTextExtractor { // Most likely negative candidates private Pattern NEGATIVE; private static final Pattern NEGATIVE_STYLE = - Pattern.compile("hidden|display: ?none|font-size: ?small"); + Pattern.compile("hidden|display: ?none|font-size: ?small"); private static final Pattern IGNORE_AUTHOR_PARTS = - Pattern.compile("by|name|author|posted|twitter|handle|news", Pattern.CASE_INSENSITIVE); + Pattern.compile("by|name|author|posted|twitter|handle|news", Pattern.CASE_INSENSITIVE); private static final Set IGNORED_TITLE_PARTS = new LinkedHashSet() { { add("hacker news"); @@ -54,12 +54,12 @@ public class ArticleTextExtractor { } }; private static final OutputFormatter DEFAULT_FORMATTER = new OutputFormatter(); - private OutputFormatter formatter = DEFAULT_FORMATTER; + private final OutputFormatter formatter = DEFAULT_FORMATTER; private static final int MAX_AUTHOR_NAME_LENGHT = 255; private static final int MIN_AUTHOR_NAME_LENGTH = 4; private static final List CLEAN_AUTHOR_PATTERNS = Collections.singletonList( - Pattern.compile("By\\S*(.*)[\\.,].*") + Pattern.compile("By\\S*(.*)[\\.,].*") ); private static final int MAX_AUTHOR_DESC_LENGHT = 1000; private static final int MAX_IMAGE_LENGHT = 255; @@ -70,14 +70,14 @@ public class ArticleTextExtractor { public ArticleTextExtractor() { setUnlikely("com(bx|ment|munity)|dis(qus|cuss)|e(xtra|[-]?mail)|foot|" - + "header|menu|re(mark|ply)|rss|sh(are|outbox)|sponsor" - + "a(d|ll|gegate|rchive|ttachment)|(pag(er|ination))|popup|print|" - + "login|si(debar|gn|ngle)"); + + "header|menu|re(mark|ply)|rss|sh(are|outbox)|sponsor" + + "a(d|ll|gegate|rchive|ttachment)|(pag(er|ination))|popup|print|" + + "login|si(debar|gn|ngle)"); setPositive("(^(body|content|h?entry|main|page|post|text|blog|story|haupt))" - + "|arti(cle|kel)|instapaper_body"); + + "|arti(cle|kel)|instapaper_body"); setNegative("nav($|igation)|user|com(ment|bx)|(^com-)|contact|" - + "foot|masthead|(me(dia|ta))|outbrain|promo|related|scroll|(sho(utbox|pping))|" - + "sidebar|sponsor|tags|tool|widget|player|disclaimer|toc|infobox|vcard"); + + "foot|masthead|(me(dia|ta))|outbrain|promo|related|scroll|(sho(utbox|pping))|" + + "sidebar|sponsor|tags|tool|widget|player|disclaimer|toc|infobox|vcard"); } @NonNull @@ -277,7 +277,7 @@ private JResult extractContent(@NonNull JResult res, @Nullable Document doc, @No // Sanity checks in author description. String authorDescSnippet = getSnippet(res.getAuthorDescription()); if (getSnippet(res.getText()).equals(authorDescSnippet) || - getSnippet(res.getDescription()).equals(authorDescSnippet)) { + getSnippet(res.getDescription()).equals(authorDescSnippet)) { res.setAuthorDescription(""); } else { if (res.getAuthorDescription().length() > MAX_AUTHOR_DESC_LENGHT) { @@ -384,8 +384,7 @@ private static Date extractDate(@NonNull Document doc) { dateStr = dateStr.substring(0, dateStr.length() - 1) + "GMT-00:00"; } else { dateStr = String.format(dateStr.substring(0, dateStr.length() - 6), - dateStr.substring(dateStr.length() - 6, - dateStr.length())); + dateStr.substring(dateStr.length() - 6)); } } catch (StringIndexOutOfBoundsException ex) { // do nothing @@ -825,7 +824,7 @@ private int weightChildNodes(@NonNull Element rootEl) { // instead penalize the grandparent. This is done to try to // avoid giving weigths to navigation nodes, etc. if (NEGATIVE.matcher(child2.id()).find() || - NEGATIVE.matcher(child2.className()).find()) { + NEGATIVE.matcher(child2.className()).find()) { grandChildrenWeight -= 30; continue; } @@ -1037,7 +1036,7 @@ protected void stripUnlikelyCandidates(@NonNull Document doc) { String id = child.id().toLowerCase(); if (NEGATIVE.matcher(className).find() - || NEGATIVE.matcher(id).find()) { + || NEGATIVE.matcher(id).find()) { child.remove(); } } diff --git a/app/src/main/java/acr/browser/lightning/reading/SHelper.java b/app/src/main/java/acr/browser/lightning/reading/SHelper.java index 0138dc800..be3550a05 100644 --- a/app/src/main/java/acr/browser/lightning/reading/SHelper.java +++ b/app/src/main/java/acr/browser/lightning/reading/SHelper.java @@ -120,7 +120,7 @@ public static String encodingCleanup(@NonNull String str) { * @return the longest substring as str1.substring(result[0], result[1]); */ public static String getLongestSubstring(@NonNull String str1, String str2) { - int res[] = longestSubstring(str1, str2); + int[] res = longestSubstring(str1, str2); if (res == null || res[0] >= res[1]) return ""; @@ -261,7 +261,7 @@ public static void enableUserAgentOverwrite() { public static String getUrlFromUglyGoogleRedirect(@NonNull String url) { if (url.startsWith("https://www.google.com/url?")) { url = url.substring("https://www.google.com/url?".length()); - String arr[] = urlDecode(url).split("&"); + String[] arr = urlDecode(url).split("&"); for (String str : arr) { if (str.startsWith("q=")) return str.substring("q=".length()); @@ -334,7 +334,7 @@ public static String estimateDate(@NonNull String url) { int month = -1; int monthCounter = -1; int day = -1; - String strs[] = url.split("/"); + String[] strs = url.split("/"); for (int counter = 0; counter < strs.length; counter++) { String str = strs[counter]; if (str.length() == 4) { diff --git a/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java b/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java index b05ce7c24..0e8a33ff8 100644 --- a/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java +++ b/app/src/main/java/acr/browser/lightning/reading/activity/ReadingActivity.java @@ -20,7 +20,6 @@ import acr.browser.lightning.BrowserApp; import acr.browser.lightning.R; -import acr.browser.lightning.constant.Constants; import acr.browser.lightning.di.MainScheduler; import acr.browser.lightning.di.NetworkScheduler; import acr.browser.lightning.dialog.BrowserDialog; @@ -42,15 +41,17 @@ public class ReadingActivity extends AppCompatActivity { + private static final String LOAD_READING_URL = "ReadingUrl"; + /** * Launches this activity with the necessary URL argument. * * @param context The context needed to launch the activity. - * @param url The URL that will be loaded into reading mode. + * @param url The URL that will be loaded into reading mode. */ public static void launch(@NonNull Context context, @NonNull String url) { final Intent intent = new Intent(context, ReadingActivity.class); - intent.putExtra(Constants.LOAD_READING_URL, url); + intent.putExtra(LOAD_READING_URL, url); context.startActivity(intent); } @@ -145,7 +146,7 @@ private boolean loadPage(@Nullable Intent intent) { if (intent == null) { return false; } - mUrl = intent.getStringExtra(Constants.LOAD_READING_URL); + mUrl = intent.getStringExtra(LOAD_READING_URL); if (mUrl == null) { return false; } @@ -273,10 +274,10 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { switch (item.getItemId()) { case R.id.invert_item: mUserPreferences.setInvertColors(!mInvert); - Intent read = new Intent(this, ReadingActivity.class); - read.putExtra(Constants.LOAD_READING_URL, mUrl); - startActivity(read); - finish(); + if (mUrl != null) { + ReadingActivity.launch(this, mUrl); + finish(); + } break; case R.id.text_size_item: diff --git a/app/src/main/java/acr/browser/lightning/search/SearchEngineProvider.kt b/app/src/main/java/acr/browser/lightning/search/SearchEngineProvider.kt index 84dfe39c2..5577a3aa5 100644 --- a/app/src/main/java/acr/browser/lightning/search/SearchEngineProvider.kt +++ b/app/src/main/java/acr/browser/lightning/search/SearchEngineProvider.kt @@ -6,6 +6,7 @@ import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.search.engine.* import acr.browser.lightning.search.suggestions.* import android.app.Application +import dagger.Reusable import io.reactivex.Single import okhttp3.OkHttpClient import javax.inject.Inject @@ -14,6 +15,7 @@ import javax.inject.Inject * The model that provides the search engine based * on the user's preference. */ +@Reusable class SearchEngineProvider @Inject constructor( private val userPreferences: UserPreferences, @SuggestionsClient private val okHttpClient: Single, diff --git a/app/src/main/java/acr/browser/lightning/settings/activity/ThemableSettingsActivity.kt b/app/src/main/java/acr/browser/lightning/settings/activity/ThemableSettingsActivity.kt index 7f726bda7..5d84a9aa8 100644 --- a/app/src/main/java/acr/browser/lightning/settings/activity/ThemableSettingsActivity.kt +++ b/app/src/main/java/acr/browser/lightning/settings/activity/ThemableSettingsActivity.kt @@ -1,5 +1,6 @@ package acr.browser.lightning.settings.activity +import acr.browser.lightning.AppTheme import acr.browser.lightning.R import acr.browser.lightning.di.injector import acr.browser.lightning.preference.UserPreferences @@ -12,7 +13,7 @@ import javax.inject.Inject abstract class ThemableSettingsActivity : AppCompatPreferenceActivity() { - private var themeId: Int = 0 + private var themeId: AppTheme = AppTheme.LIGHT @Inject internal lateinit var userPreferences: UserPreferences @@ -22,17 +23,17 @@ abstract class ThemableSettingsActivity : AppCompatPreferenceActivity() { // set the theme when (themeId) { - 0 -> { + AppTheme.LIGHT -> { setTheme(R.style.Theme_SettingsTheme) - this.window.setBackgroundDrawable(ColorDrawable(ThemeUtils.getPrimaryColor(this))) + window.setBackgroundDrawable(ColorDrawable(ThemeUtils.getPrimaryColor(this))) } - 1 -> { + AppTheme.DARK -> { setTheme(R.style.Theme_SettingsTheme_Dark) - this.window.setBackgroundDrawable(ColorDrawable(ThemeUtils.getPrimaryColorDark(this))) + window.setBackgroundDrawable(ColorDrawable(ThemeUtils.getPrimaryColorDark(this))) } - 2 -> { + AppTheme.BLACK -> { setTheme(R.style.Theme_SettingsTheme_Black) - this.window.setBackgroundDrawable(ColorDrawable(ThemeUtils.getPrimaryColorDark(this))) + window.setBackgroundDrawable(ColorDrawable(ThemeUtils.getPrimaryColorDark(this))) } } super.onCreate(savedInstanceState) diff --git a/app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.kt b/app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.kt index a087f1bce..5dbe56157 100644 --- a/app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.kt +++ b/app/src/main/java/acr/browser/lightning/settings/fragment/AdvancedSettingsFragment.kt @@ -1,12 +1,14 @@ package acr.browser.lightning.settings.fragment import acr.browser.lightning.R +import acr.browser.lightning.browser.SearchBoxDisplayChoice import acr.browser.lightning.constant.TEXT_ENCODINGS import acr.browser.lightning.di.injector -import acr.browser.lightning.dialog.BrowserDialog +import acr.browser.lightning.extensions.resizeAndShow +import acr.browser.lightning.extensions.withSingleChoiceItems import acr.browser.lightning.preference.UserPreferences +import acr.browser.lightning.view.RenderingMode import android.os.Bundle -import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import javax.inject.Inject @@ -26,7 +28,7 @@ class AdvancedSettingsFragment : AbstractSettingsFragment() { clickableDynamicPreference( preference = SETTINGS_RENDERING_MODE, - summary = getString(renderingModePreferenceToString(userPreferences.renderingMode)), + summary = userPreferences.renderingMode.toDisplayString(), onClick = this::showRenderingDialogPicker ) @@ -38,7 +40,7 @@ class AdvancedSettingsFragment : AbstractSettingsFragment() { clickableDynamicPreference( preference = SETTINGS_URL_CONTENT, - summary = urlBoxPreferenceToString(userPreferences.urlBoxContentChoice), + summary = userPreferences.urlBoxContentChoice.toDisplayString(), onClick = this::showUrlBoxDialogPicker ) @@ -73,28 +75,17 @@ class AdvancedSettingsFragment : AbstractSettingsFragment() { * @param summaryUpdater the command which allows the summary to be updated. */ private fun showRenderingDialogPicker(summaryUpdater: SummaryUpdater) { - activity?.let { - - val dialog = AlertDialog.Builder(it).apply { - setTitle(resources.getString(R.string.rendering_mode)) - - val choices = arrayOf( - it.getString(R.string.name_normal), - it.getString(R.string.name_inverted), - it.getString(R.string.name_grayscale), - it.getString(R.string.name_inverted_grayscale), - it.getString(R.string.name_increase_contrast) - ) + activity?.let { AlertDialog.Builder(it) }?.apply { + setTitle(resources.getString(R.string.rendering_mode)) - setSingleChoiceItems(choices, userPreferences.renderingMode) { _, which -> - userPreferences.renderingMode = which - summaryUpdater.updateSummary(getString(renderingModePreferenceToString(which))) - } - setPositiveButton(resources.getString(R.string.action_ok), null) - }.show() + val values = RenderingMode.values().map { Pair(it, it.toDisplayString()) } + withSingleChoiceItems(values, userPreferences.renderingMode) { + userPreferences.renderingMode = it + summaryUpdater.updateSummary(it.toDisplayString()) - BrowserDialog.setDialogSize(it, dialog) - } + } + setPositiveButton(resources.getString(R.string.action_ok), null) + }?.resizeAndShow() } @@ -105,7 +96,7 @@ class AdvancedSettingsFragment : AbstractSettingsFragment() { */ private fun showTextEncodingDialogPicker(summaryUpdater: SummaryUpdater) { activity?.let { - val dialog = AlertDialog.Builder(it).apply { + AlertDialog.Builder(it).apply { setTitle(resources.getString(R.string.text_encoding)) val currentChoice = TEXT_ENCODINGS.indexOf(userPreferences.textEncoding) @@ -115,9 +106,7 @@ class AdvancedSettingsFragment : AbstractSettingsFragment() { summaryUpdater.updateSummary(TEXT_ENCODINGS[which]) } setPositiveButton(resources.getString(R.string.action_ok), null) - }.show() - - BrowserDialog.setDialogSize(it, dialog) + }.resizeAndShow() } } @@ -127,46 +116,36 @@ class AdvancedSettingsFragment : AbstractSettingsFragment() { * @param summaryUpdater the command which allows the summary to be updated. */ private fun showUrlBoxDialogPicker(summaryUpdater: SummaryUpdater) { - activity?.let { - val dialog = AlertDialog.Builder(it).apply { - setTitle(resources.getString(R.string.url_contents)) + activity?.let { AlertDialog.Builder(it) }?.apply { + setTitle(resources.getString(R.string.url_contents)) - val array = resources.getStringArray(R.array.url_content_array) - - setSingleChoiceItems(array, userPreferences.urlBoxContentChoice) { _, which -> - userPreferences.urlBoxContentChoice = which - summaryUpdater.updateSummary(urlBoxPreferenceToString(which)) - } - setPositiveButton(resources.getString(R.string.action_ok), null) - }.show() - BrowserDialog.setDialogSize(it, dialog) - } - } + val items = SearchBoxDisplayChoice.values().map { Pair(it, it.toDisplayString()) } - /** - * Convert an integer to the [StringRes] representation which can be displayed to the user for - * the rendering mode preference. - */ - @StringRes - private fun renderingModePreferenceToString(preference: Int): Int = when (preference) { - 0 -> R.string.name_normal - 1 -> R.string.name_inverted - 2 -> R.string.name_grayscale - 3 -> R.string.name_inverted_grayscale - 4 -> R.string.name_increase_contrast - else -> throw IllegalArgumentException("Unknown rendering mode preference $preference") + withSingleChoiceItems(items, userPreferences.urlBoxContentChoice) { + userPreferences.urlBoxContentChoice = it + summaryUpdater.updateSummary(it.toDisplayString()) + } + setPositiveButton(resources.getString(R.string.action_ok), null) + }?.resizeAndShow() } - /** - * Convert an integer to the [String] representation which can be displayed to the user for the - * URL box preference. - */ - private fun urlBoxPreferenceToString(preference: Int): String { + private fun SearchBoxDisplayChoice.toDisplayString(): String { val stringArray = resources.getStringArray(R.array.url_content_array) - - return stringArray[preference] + return when (this) { + SearchBoxDisplayChoice.DOMAIN -> stringArray[0] + SearchBoxDisplayChoice.URL -> stringArray[1] + SearchBoxDisplayChoice.TITLE -> stringArray[2] + } } + private fun RenderingMode.toDisplayString(): String = getString(when (this) { + RenderingMode.NORMAL -> R.string.name_normal + RenderingMode.INVERTED -> R.string.name_inverted + RenderingMode.GRAYSCALE -> R.string.name_grayscale + RenderingMode.INVERTED_GRAYSCALE -> R.string.name_inverted_grayscale + RenderingMode.INCREASE_CONTRAST -> R.string.name_increase_contrast + }) + companion object { private const val SETTINGS_NEW_WINDOW = "allow_new_window" private const val SETTINGS_ENABLE_COOKIES = "allow_cookies" diff --git a/app/src/main/java/acr/browser/lightning/settings/fragment/BookmarkSettingsFragment.kt b/app/src/main/java/acr/browser/lightning/settings/fragment/BookmarkSettingsFragment.kt index cfe137d72..1327c40c9 100644 --- a/app/src/main/java/acr/browser/lightning/settings/fragment/BookmarkSettingsFragment.kt +++ b/app/src/main/java/acr/browser/lightning/settings/fragment/BookmarkSettingsFragment.kt @@ -11,6 +11,7 @@ import acr.browser.lightning.di.MainScheduler import acr.browser.lightning.di.injector import acr.browser.lightning.dialog.BrowserDialog import acr.browser.lightning.dialog.DialogItem +import acr.browser.lightning.extensions.resizeAndShow import acr.browser.lightning.extensions.snackbar import acr.browser.lightning.extensions.toast import acr.browser.lightning.log.Logger @@ -223,8 +224,7 @@ class BookmarkSettingsFragment : AbstractSettingsFragment() { ) } } - val dialog = builder.show() - BrowserDialog.setDialogSize(activity, dialog) + builder.resizeAndShow() } companion object { diff --git a/app/src/main/java/acr/browser/lightning/settings/fragment/DisplaySettingsFragment.kt b/app/src/main/java/acr/browser/lightning/settings/fragment/DisplaySettingsFragment.kt index 987adaf44..dfcf7e4dd 100644 --- a/app/src/main/java/acr/browser/lightning/settings/fragment/DisplaySettingsFragment.kt +++ b/app/src/main/java/acr/browser/lightning/settings/fragment/DisplaySettingsFragment.kt @@ -3,9 +3,11 @@ */ package acr.browser.lightning.settings.fragment +import acr.browser.lightning.AppTheme import acr.browser.lightning.R import acr.browser.lightning.di.injector -import acr.browser.lightning.dialog.BrowserDialog +import acr.browser.lightning.extensions.resizeAndShow +import acr.browser.lightning.extensions.withSingleChoiceItems import acr.browser.lightning.preference.UserPreferences import android.os.Bundle import android.view.Gravity @@ -31,17 +33,15 @@ class DisplaySettingsFragment : AbstractSettingsFragment() { injector.inject(this) // preferences storage - themeOptions = this.resources.getStringArray(R.array.themes) - clickableDynamicPreference( preference = SETTINGS_THEME, - summary = themeOptions[userPreferences.useTheme], - onClick = this::showThemePicker + summary = userPreferences.useTheme.toDisplayString(), + onClick = ::showThemePicker ) clickablePreference( preference = SETTINGS_TEXTSIZE, - onClick = this::showTextSizePicker + onClick = ::showTextSizePicker ) checkBoxPreference( @@ -95,7 +95,7 @@ class DisplaySettingsFragment : AbstractSettingsFragment() { private fun showTextSizePicker() { val maxValue = 5 - val dialog = AlertDialog.Builder(activity).apply { + AlertDialog.Builder(activity).apply { val layoutInflater = activity.layoutInflater val customView = (layoutInflater.inflate(R.layout.dialog_seek_bar, null) as LinearLayout).apply { val text = TextView(activity).apply { @@ -116,21 +116,17 @@ class DisplaySettingsFragment : AbstractSettingsFragment() { val seekBar = customView.findViewById(R.id.text_size_seekbar) userPreferences.textSize = maxValue - seekBar.progress } - }.show() - - BrowserDialog.setDialogSize(activity, dialog) + }.resizeAndShow() } private fun showThemePicker(summaryUpdater: SummaryUpdater) { val currentTheme = userPreferences.useTheme - - val dialog = AlertDialog.Builder(activity).apply { + AlertDialog.Builder(activity).apply { setTitle(resources.getString(R.string.theme)) - setSingleChoiceItems(themeOptions, currentTheme) { _, which -> - userPreferences.useTheme = which - if (which < themeOptions.size) { - summaryUpdater.updateSummary(themeOptions[which]) - } + val values = AppTheme.values().map { Pair(it, it.toDisplayString()) } + withSingleChoiceItems(values, userPreferences.useTheme) { + userPreferences.useTheme = it + summaryUpdater.updateSummary(it.toDisplayString()) } setPositiveButton(resources.getString(R.string.action_ok)) { _, _ -> if (currentTheme != userPreferences.useTheme) { @@ -142,11 +138,15 @@ class DisplaySettingsFragment : AbstractSettingsFragment() { activity.onBackPressed() } } - }.show() - - BrowserDialog.setDialogSize(activity, dialog) + }.resizeAndShow() } + private fun AppTheme.toDisplayString(): String = getString(when (this) { + AppTheme.LIGHT -> R.string.light_theme + AppTheme.DARK -> R.string.dark_theme + AppTheme.BLACK -> R.string.black_theme + }) + private class TextSeekBarListener( private val sampleText: TextView ) : SeekBar.OnSeekBarChangeListener { diff --git a/app/src/main/java/acr/browser/lightning/settings/fragment/GeneralSettingsFragment.kt b/app/src/main/java/acr/browser/lightning/settings/fragment/GeneralSettingsFragment.kt index b468d5105..60e326d5f 100644 --- a/app/src/main/java/acr/browser/lightning/settings/fragment/GeneralSettingsFragment.kt +++ b/app/src/main/java/acr/browser/lightning/settings/fragment/GeneralSettingsFragment.kt @@ -1,9 +1,13 @@ package acr.browser.lightning.settings.fragment import acr.browser.lightning.R -import acr.browser.lightning.constant.* +import acr.browser.lightning.browser.ProxyChoice +import acr.browser.lightning.constant.SCHEME_BLANK +import acr.browser.lightning.constant.SCHEME_BOOKMARKS +import acr.browser.lightning.constant.SCHEME_HOMEPAGE import acr.browser.lightning.di.injector import acr.browser.lightning.dialog.BrowserDialog +import acr.browser.lightning.extensions.withSingleChoiceItems import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.search.SearchEngineProvider import acr.browser.lightning.search.Suggestions @@ -46,38 +50,38 @@ class GeneralSettingsFragment : AbstractSettingsFragment() { clickableDynamicPreference( preference = SETTINGS_PROXY, - summary = proxyChoiceToSummary(userPreferences.proxyChoice), - onClick = this::showProxyPicker + summary = userPreferences.proxyChoice.toSummary(), + onClick = ::showProxyPicker ) clickableDynamicPreference( preference = SETTINGS_USER_AGENT, summary = choiceToUserAgent(userPreferences.userAgentChoice), - onClick = this::showUserAgentChooserDialog + onClick = ::showUserAgentChooserDialog ) clickableDynamicPreference( preference = SETTINGS_DOWNLOAD, summary = userPreferences.downloadDirectory, - onClick = this::showDownloadLocationDialog + onClick = ::showDownloadLocationDialog ) clickableDynamicPreference( preference = SETTINGS_HOME, summary = homePageUrlToDisplayTitle(userPreferences.homepage), - onClick = this::showHomePageDialog + onClick = ::showHomePageDialog ) clickableDynamicPreference( preference = SETTINGS_SEARCH_ENGINE, summary = getSearchEngineSummary(searchEngineProvider.provideSearchEngine()), - onClick = this::showSearchProviderDialog + onClick = ::showSearchProviderDialog ) clickableDynamicPreference( preference = SETTINGS_SUGGESTIONS, summary = searchSuggestionChoiceToTitle(Suggestions.from(userPreferences.searchSuggestionChoice)), - onClick = this::showSearchSuggestionsDialog + onClick = ::showSearchSuggestionsDialog ) checkBoxPreference( @@ -105,37 +109,43 @@ class GeneralSettingsFragment : AbstractSettingsFragment() { ) } - private fun proxyChoiceToSummary(choice: Int) = when (choice) { - PROXY_MANUAL -> "${userPreferences.proxyHost}:${userPreferences.proxyPort}" - NO_PROXY, - PROXY_ORBOT, - PROXY_I2P -> proxyChoices[choice] - else -> proxyChoices[NO_PROXY] + private fun ProxyChoice.toSummary(): String { + val stringArray = resources.getStringArray(R.array.proxy_choices_array) + return when (this) { + ProxyChoice.NONE -> stringArray[0] + ProxyChoice.ORBOT -> stringArray[1] + ProxyChoice.I2P -> stringArray[2] + ProxyChoice.MANUAL -> "${userPreferences.proxyHost}:${userPreferences.proxyPort}" + } } private fun showProxyPicker(summaryUpdater: SummaryUpdater) { BrowserDialog.showCustomDialog(activity) { setTitle(R.string.http_proxy) - setSingleChoiceItems(proxyChoices, userPreferences.proxyChoice) { _, which -> - updateProxyChoice(which, it, summaryUpdater) + val stringArray = resources.getStringArray(R.array.proxy_choices_array) + val values = ProxyChoice.values().map { + Pair(it, when (it) { + ProxyChoice.NONE -> stringArray[0] + ProxyChoice.ORBOT -> stringArray[1] + ProxyChoice.I2P -> stringArray[2] + ProxyChoice.MANUAL -> stringArray[3] + }) + } + withSingleChoiceItems(values, userPreferences.proxyChoice) { + updateProxyChoice(it, activity, summaryUpdater) } setPositiveButton(R.string.action_ok, null) } } - private fun updateProxyChoice(@Proxy choice: Int, activity: Activity, summaryUpdater: SummaryUpdater) { + private fun updateProxyChoice(choice: ProxyChoice, activity: Activity, summaryUpdater: SummaryUpdater) { val sanitizedChoice = ProxyUtils.sanitizeProxyChoice(choice, activity) - when (sanitizedChoice) { - PROXY_ORBOT, - PROXY_I2P, - NO_PROXY -> Unit - PROXY_MANUAL -> showManualProxyPicker(activity, summaryUpdater) + if (sanitizedChoice == ProxyChoice.MANUAL) { + showManualProxyPicker(activity, summaryUpdater) } userPreferences.proxyChoice = sanitizedChoice - if (sanitizedChoice < proxyChoices.size) { - summaryUpdater.updateSummary(proxyChoices[sanitizedChoice]) - } + summaryUpdater.updateSummary(sanitizedChoice.toSummary()) } private fun showManualProxyPicker(activity: Activity, summaryUpdater: SummaryUpdater) { diff --git a/app/src/main/java/acr/browser/lightning/ssl/SslDialog.kt b/app/src/main/java/acr/browser/lightning/ssl/SslDialog.kt new file mode 100644 index 000000000..2f11e55d9 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/ssl/SslDialog.kt @@ -0,0 +1,40 @@ +package acr.browser.lightning.ssl + +import acr.browser.lightning.R +import acr.browser.lightning.extensions.inflater +import acr.browser.lightning.extensions.resizeAndShow +import android.content.Context +import android.net.http.SslCertificate +import android.text.format.DateFormat +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import kotlinx.android.synthetic.main.dialog_ssl_info.view.* + +/** + * Shows an informative dialog with the provided [SslCertificate] information. + */ +fun Context.showSslDialog(sslCertificate: SslCertificate, sslState: SslState) { + val by = sslCertificate.issuedBy + val to = sslCertificate.issuedTo + val issueDate = sslCertificate.validNotBeforeDate + val expireDate = sslCertificate.validNotAfterDate + + 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) + } + + val icon = createSslDrawableForState(sslState) + + AlertDialog.Builder(this) + .setIcon(icon) + .setTitle(to.cName) + .setView(contentView) + .setPositiveButton(R.string.action_ok, null) + .resizeAndShow() +} diff --git a/app/src/main/java/acr/browser/lightning/ssl/SslIcon.kt b/app/src/main/java/acr/browser/lightning/ssl/SslIcon.kt new file mode 100644 index 000000000..eb5afa525 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/ssl/SslIcon.kt @@ -0,0 +1,24 @@ +package acr.browser.lightning.ssl + +import acr.browser.lightning.R +import acr.browser.lightning.utils.DrawableUtils +import android.content.Context +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable + +/** + * Creates the proper [Drawable] to represent the [SslState]. + */ +fun Context.createSslDrawableForState(sslState: SslState): Drawable? = when (sslState) { + is SslState.None -> null + is SslState.Valid -> { + val bitmap = DrawableUtils.createImageInsetInRoundedSquare(this, R.drawable.ic_secured) + val securedDrawable = BitmapDrawable(resources, bitmap) + securedDrawable + } + is SslState.Invalid -> { + val bitmap = DrawableUtils.createImageInsetInRoundedSquare(this, R.drawable.ic_unsecured) + val unsecuredDrawable = BitmapDrawable(resources, bitmap) + unsecuredDrawable + } +} diff --git a/app/src/main/java/acr/browser/lightning/ssl/SSLState.kt b/app/src/main/java/acr/browser/lightning/ssl/SslState.kt similarity index 70% rename from app/src/main/java/acr/browser/lightning/ssl/SSLState.kt rename to app/src/main/java/acr/browser/lightning/ssl/SslState.kt index 76108fee9..fa0f8312c 100644 --- a/app/src/main/java/acr/browser/lightning/ssl/SSLState.kt +++ b/app/src/main/java/acr/browser/lightning/ssl/SslState.kt @@ -5,23 +5,23 @@ import android.net.http.SslError /** * Representing the SSL state of the browser. */ -sealed class SSLState { +sealed class SslState { /** * No SSL. */ - object None : SSLState() + object None : SslState() /** * Valid SSL connection. */ - object Valid : SSLState() + object Valid : SslState() /** * Broken SSL connection. * * @param sslError The error that is causing the invalid SSL state. */ - class Invalid(val sslError: SslError) : SSLState() + class Invalid(val sslError: SslError) : SslState() } diff --git a/app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java b/app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java index 8b1db0031..47cbdd5ca 100644 --- a/app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java +++ b/app/src/main/java/acr/browser/lightning/utils/ProxyUtils.java @@ -7,21 +7,27 @@ import net.i2p.android.ui.I2PAndroidHelper; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import javax.inject.Inject; import javax.inject.Singleton; import acr.browser.lightning.BrowserApp; import acr.browser.lightning.R; -import acr.browser.lightning.constant.Constants; -import acr.browser.lightning.constant.Proxy; +import acr.browser.lightning.browser.ProxyChoice; import acr.browser.lightning.dialog.BrowserDialog; import acr.browser.lightning.extensions.ActivityExtensions; +import acr.browser.lightning.extensions.AlertDialogExtensionsKt; import acr.browser.lightning.preference.DeveloperPreferences; import acr.browser.lightning.preference.UserPreferences; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import info.guardianproject.netcipher.proxy.OrbotHelper; import info.guardianproject.netcipher.webkit.WebkitProxy; +import kotlin.Pair; +import kotlin.Unit; @Singleton public final class ProxyUtils { @@ -46,7 +52,7 @@ public ProxyUtils() { * proxying for this session */ public void checkForProxy(@NonNull final Activity activity) { - int proxyChoice = mUserPreferences.getProxyChoice(); + final ProxyChoice currentProxyChoice = mUserPreferences.getProxyChoice(); final boolean orbotInstalled = OrbotHelper.isOrbotInstalled(activity); boolean orbotChecked = mDeveloperPreferences.getCheckedForTor(); @@ -56,8 +62,8 @@ public void checkForProxy(@NonNull final Activity activity) { boolean i2pChecked = mDeveloperPreferences.getCheckedForI2P(); boolean i2p = i2pInstalled && !i2pChecked; - // TODO Is the idea to show this per-session, or only once? - if (proxyChoice != Constants.NO_PROXY && (orbot || i2p)) { + // Do only once per install + if (currentProxyChoice != ProxyChoice.NONE && (orbot || i2p)) { if (orbot) { mDeveloperPreferences.setCheckedForTor(true); } @@ -68,25 +74,33 @@ public void checkForProxy(@NonNull final Activity activity) { if (orbotInstalled && i2pInstalled) { String[] proxyChoices = activity.getResources().getStringArray(R.array.proxy_choices_array); - builder.setTitle(activity.getResources().getString(R.string.http_proxy)) - .setSingleChoiceItems(proxyChoices, mUserPreferences.getProxyChoice(), - (dialog, which) -> mUserPreferences.setProxyChoice(which)) - .setPositiveButton(activity.getResources().getString(R.string.action_ok), - (dialog, which) -> { - if (mUserPreferences.getProxyChoice() != Constants.NO_PROXY) { - initializeProxy(activity); - } - }); + final List values = Arrays.asList(ProxyChoice.NONE, ProxyChoice.ORBOT, ProxyChoice.I2P); + final List> list = new ArrayList<>(); + for (ProxyChoice proxyChoice : values) { + list.add(new Pair<>(proxyChoice, proxyChoices[proxyChoice.getValue()])); + } + builder.setTitle(activity.getResources().getString(R.string.http_proxy)); + AlertDialogExtensionsKt.withSingleChoiceItems(builder, list, mUserPreferences.getProxyChoice(), newProxyChoice -> { + mUserPreferences.setProxyChoice(newProxyChoice); + return Unit.INSTANCE; + }); + builder.setPositiveButton(activity.getResources().getString(R.string.action_ok), + (dialog, which) -> { + if (mUserPreferences.getProxyChoice() != ProxyChoice.NONE) { + initializeProxy(activity); + } + }); } else { DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> { switch (which) { case DialogInterface.BUTTON_POSITIVE: - mUserPreferences.setProxyChoice(orbotInstalled ? - Constants.PROXY_ORBOT : Constants.PROXY_I2P); + mUserPreferences.setProxyChoice(orbotInstalled + ? ProxyChoice.ORBOT + : ProxyChoice.I2P); initializeProxy(activity); break; case DialogInterface.BUTTON_NEGATIVE: - mUserPreferences.setProxyChoice(Constants.NO_PROXY); + mUserPreferences.setProxyChoice(ProxyChoice.NONE); break; } }; @@ -108,17 +122,17 @@ private void initializeProxy(@NonNull Activity activity) { int port; switch (mUserPreferences.getProxyChoice()) { - case Constants.NO_PROXY: + case NONE: // We shouldn't be here return; - case Constants.PROXY_ORBOT: + case ORBOT: if (!OrbotHelper.isOrbotRunning(activity)) { OrbotHelper.requestStartTor(activity); } host = "localhost"; port = 8118; break; - case Constants.PROXY_I2P: + case I2P: sI2PProxyInitialized = true; if (sI2PHelperBound && !mI2PHelper.isI2PAndroidRunning()) { mI2PHelper.requestI2PAndroidStart(activity); @@ -127,10 +141,7 @@ private void initializeProxy(@NonNull Activity activity) { port = 4444; break; default: - host = mUserPreferences.getProxyHost(); - port = mUserPreferences.getProxyPort(); - break; - case Constants.PROXY_MANUAL: + case MANUAL: host = mUserPreferences.getProxyHost(); port = mUserPreferences.getProxyPort(); break; @@ -145,7 +156,7 @@ private void initializeProxy(@NonNull Activity activity) { } public boolean isProxyReady(@NonNull Activity activity) { - if (mUserPreferences.getProxyChoice() == Constants.PROXY_I2P) { + if (mUserPreferences.getProxyChoice() == ProxyChoice.I2P) { if (!mI2PHelper.isI2PAndroidRunning()) { ActivityExtensions.snackbar(activity, R.string.i2p_not_running); return false; @@ -159,7 +170,7 @@ public boolean isProxyReady(@NonNull Activity activity) { } public void updateProxySettings(@NonNull Activity activity) { - if (mUserPreferences.getProxyChoice() != Constants.NO_PROXY) { + if (mUserPreferences.getProxyChoice() != ProxyChoice.NONE) { initializeProxy(activity); } else { try { @@ -178,7 +189,7 @@ public void onStop() { } public void onStart(final Activity activity) { - if (mUserPreferences.getProxyChoice() == Constants.PROXY_I2P) { + if (mUserPreferences.getProxyChoice() == ProxyChoice.I2P) { // Try to bind to I2P Android mI2PHelper.bind(() -> { sI2PHelperBound = true; @@ -188,23 +199,22 @@ public void onStart(final Activity activity) { } } - @Proxy - public static int sanitizeProxyChoice(int choice, @NonNull Activity activity) { + public static ProxyChoice sanitizeProxyChoice(ProxyChoice choice, @NonNull Activity activity) { switch (choice) { - case Constants.PROXY_ORBOT: + case ORBOT: if (!OrbotHelper.isOrbotInstalled(activity)) { - choice = Constants.NO_PROXY; + choice = ProxyChoice.NONE; ActivityExtensions.snackbar(activity, R.string.install_orbot); } break; - case Constants.PROXY_I2P: + case I2P: I2PAndroidHelper ih = new I2PAndroidHelper(activity.getApplication()); if (!ih.isI2PAndroidInstalled()) { - choice = Constants.NO_PROXY; + choice = ProxyChoice.NONE; ih.promptToInstall(activity); } break; - case Constants.PROXY_MANUAL: + case MANUAL: break; } return choice; diff --git a/app/src/main/java/acr/browser/lightning/view/LightningView.kt b/app/src/main/java/acr/browser/lightning/view/LightningView.kt index 06f07cf04..92145eef4 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningView.kt +++ b/app/src/main/java/acr/browser/lightning/view/LightningView.kt @@ -15,12 +15,13 @@ import acr.browser.lightning.log.Logger import acr.browser.lightning.network.NetworkConnectivityModel import acr.browser.lightning.preference.UserPreferences import acr.browser.lightning.preference.userAgent -import acr.browser.lightning.ssl.SSLState +import acr.browser.lightning.ssl.SslState import acr.browser.lightning.utils.* import acr.browser.lightning.view.find.FindResults import android.annotation.SuppressLint import android.app.Activity import android.graphics.* +import android.net.http.SslCertificate import android.os.Build import android.os.Bundle import android.os.Handler @@ -168,6 +169,12 @@ class LightningView( val title: String get() = titleInfo.getTitle() ?: "" + /** + * Get the current [SslCertificate] if there is any associated with the current page. + */ + val sslCertificate: SslCertificate? + get() = webView?.certificate + /** * Get the current URL of the WebView, or an empty string if the WebView is null or the URL is * null. @@ -221,9 +228,9 @@ class LightningView( .subscribe(::setNetworkAvailable) } - fun currentSslState(): SSLState = lightningWebClient.sslState + fun currentSslState(): SslState = lightningWebClient.sslState - fun sslStateObservable(): Observable = lightningWebClient.sslStateObservable() + fun sslStateObservable(): Observable = lightningWebClient.sslStateObservable() /** * This method loads the homepage for the browser. Either it loads the URL stored as the @@ -261,6 +268,10 @@ class LightningView( lightningWebClient.updatePreferences() + val modifiesHeaders = userPreferences.doNotTrackEnabled + || userPreferences.saveDataEnabled + || userPreferences.removeIdentifyingHeadersEnabled + if (userPreferences.doNotTrackEnabled) { requestHeaders[HEADER_DNT] = "1" } else { @@ -317,7 +328,8 @@ class LightningView( settings.blockNetworkImage = userPreferences.blockImagesEnabled if (!isIncognito) { - settings.setSupportMultipleWindows(userPreferences.popupsEnabled) + // Modifying headers causes SEGFAULTS, so disallow multi window if headers are enabled. + settings.setSupportMultipleWindows(userPreferences.popupsEnabled && !modifiesHeaders) } else { settings.setSupportMultipleWindows(false) } @@ -481,17 +493,17 @@ class LightningView( /** * Sets the current rendering color of the WebView instance * of the current LightningView. The for modes are normal - * rendering (0), inverted rendering (1), grayscale rendering (2), - * and inverted grayscale rendering (3) + * rendering, inverted rendering, grayscale rendering, + * and inverted grayscale rendering * * @param mode the integer mode to set as the rendering mode. * see the numbers in documentation above for the * values this method accepts. */ - private fun setColorMode(mode: Int) { + private fun setColorMode(mode: RenderingMode) { invertPage = false when (mode) { - 0 -> { + RenderingMode.NORMAL -> { paint.colorFilter = null // setSoftwareRendering(); // Some devices get segfaults // in the WebView with Hardware Acceleration enabled, @@ -499,7 +511,7 @@ class LightningView( setNormalRendering() invertPage = false } - 1 -> { + RenderingMode.INVERTED -> { val filterInvert = ColorMatrixColorFilter( negativeColorArray) paint.colorFilter = filterInvert @@ -507,14 +519,14 @@ class LightningView( invertPage = true } - 2 -> { + RenderingMode.GRAYSCALE -> { val cm = ColorMatrix() cm.setSaturation(0f) val filterGray = ColorMatrixColorFilter(cm) paint.colorFilter = filterGray setHardwareRendering() } - 3 -> { + RenderingMode.INVERTED_GRAYSCALE -> { val matrix = ColorMatrix() matrix.set(negativeColorArray) val matrixGray = ColorMatrix() @@ -528,7 +540,7 @@ class LightningView( invertPage = true } - 4 -> { + RenderingMode.INCREASE_CONTRAST -> { val increaseHighContrast = ColorMatrixColorFilter(increaseContrastColorArray) paint.colorFilter = increaseHighContrast setHardwareRendering() diff --git a/app/src/main/java/acr/browser/lightning/view/LightningWebClient.kt b/app/src/main/java/acr/browser/lightning/view/LightningWebClient.kt index 5823c55dd..05c3cdedc 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningWebClient.kt +++ b/app/src/main/java/acr/browser/lightning/view/LightningWebClient.kt @@ -9,9 +9,11 @@ import acr.browser.lightning.controller.UIController import acr.browser.lightning.di.injector import acr.browser.lightning.extensions.resizeAndShow import acr.browser.lightning.extensions.snackbar +import acr.browser.lightning.js.InvertPage +import acr.browser.lightning.js.TextReflow import acr.browser.lightning.log.Logger import acr.browser.lightning.preference.UserPreferences -import acr.browser.lightning.ssl.SSLState +import acr.browser.lightning.ssl.SslState import acr.browser.lightning.ssl.SslWarningPreferences import acr.browser.lightning.utils.* import android.annotation.TargetApi @@ -30,7 +32,6 @@ import android.widget.EditText import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.content.FileProvider -import com.anthonycr.mezzanine.MezzanineGenerator import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import java.io.ByteArrayInputStream @@ -54,24 +55,25 @@ class LightningWebClient( @Inject internal lateinit var sslWarningPreferences: SslWarningPreferences @Inject internal lateinit var whitelistModel: AllowListModel @Inject internal lateinit var logger: Logger + @Inject internal lateinit var textReflowJs: TextReflow + @Inject internal lateinit var invertPageJs: InvertPage private var adBlock: AdBlocker + private var urlWithSslError: String? = null + @Volatile private var isRunning = false private var zoomScale = 0.0f - private val textReflowJs = MezzanineGenerator.TextReflow() - private val invertPageJs = MezzanineGenerator.InvertPage() - private var currentUrl: String = "" - var sslState: SSLState = SSLState.None + var sslState: SslState = SslState.None private set(value) { sslStateSubject.onNext(value) field = value } - private val sslStateSubject: PublishSubject = PublishSubject.create() + private val sslStateSubject: PublishSubject = PublishSubject.create() init { activity.injector.inject(this) @@ -79,7 +81,7 @@ class LightningWebClient( adBlock = chooseAdBlocker() } - fun sslStateObservable(): Observable = sslStateSubject.hide() + fun sslStateObservable(): Observable = sslStateSubject.hide() fun updatePreferences() { adBlock = chooseAdBlocker() @@ -133,10 +135,13 @@ class LightningWebClient( override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { currentUrl = url - sslState = if (URLUtil.isHttpsUrl(url)) { - SSLState.Valid - } else { - SSLState.None + // Only set the SSL state if there isn't an error for the current URL. + if (urlWithSslError != url) { + sslState = if (URLUtil.isHttpsUrl(url)) { + SslState.Valid + } else { + SslState.None + } } lightningView.titleInfo.setFavicon(null) if (lightningView.isShown) { @@ -146,8 +151,12 @@ class LightningWebClient( uiController.tabChanged(lightningView) } - override fun onReceivedHttpAuthRequest(view: WebView, handler: HttpAuthHandler, - host: String, realm: String) = + override fun onReceivedHttpAuthRequest( + view: WebView, + handler: HttpAuthHandler, + host: String, + realm: String + ) { AlertDialog.Builder(activity).apply { val dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_auth_request, null) @@ -170,6 +179,7 @@ class LightningWebClient( handler.cancel() } }.resizeAndShow() + } override fun onScaleChanged(view: WebView, oldScale: Float, newScale: Float) { if (view.isShown && lightningView.userPreferences.textReflowEnabled) { @@ -187,7 +197,8 @@ class LightningWebClient( } override fun onReceivedSslError(webView: WebView, handler: SslErrorHandler, error: SslError) { - sslState = SSLState.Invalid(error) + urlWithSslError = webView.url + sslState = SslState.Invalid(error) when (sslWarningPreferences.recallBehaviorForDomain(webView.url)) { SslWarningPreferences.Behavior.PROCEED -> return handler.proceed() @@ -226,7 +237,7 @@ class LightningWebClient( }.resizeAndShow() } - override fun onFormResubmission(view: WebView, dontResend: Message, resend: Message) = + override fun onFormResubmission(view: WebView, dontResend: Message, resend: Message) { AlertDialog.Builder(activity).apply { setTitle(activity.getString(R.string.title_form_resubmission)) setMessage(activity.getString(R.string.message_form_resubmission)) @@ -238,6 +249,7 @@ class LightningWebClient( dontResend.sendToTarget() } }.resizeAndShow() + } @TargetApi(Build.VERSION_CODES.LOLLIPOP) override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean = diff --git a/app/src/main/java/acr/browser/lightning/view/RenderingMode.kt b/app/src/main/java/acr/browser/lightning/view/RenderingMode.kt new file mode 100644 index 000000000..c6c03a7fa --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/view/RenderingMode.kt @@ -0,0 +1,14 @@ +package acr.browser.lightning.view + +import acr.browser.lightning.preference.IntEnum + +/** + * An enum representing the browser's available rendering modes. + */ +enum class RenderingMode(override val value: Int) : IntEnum { + NORMAL(0), + INVERTED(1), + GRAYSCALE(2), + INVERTED_GRAYSCALE(3), + INCREASE_CONTRAST(4) +} diff --git a/app/src/main/java/acr/browser/lightning/view/TabInitializer.kt b/app/src/main/java/acr/browser/lightning/view/TabInitializer.kt index ef1b167b5..54e83ec64 100644 --- a/app/src/main/java/acr/browser/lightning/view/TabInitializer.kt +++ b/app/src/main/java/acr/browser/lightning/view/TabInitializer.kt @@ -17,6 +17,7 @@ import android.os.Bundle import android.os.Message import android.webkit.WebView import androidx.appcompat.app.AlertDialog +import dagger.Reusable import io.reactivex.Scheduler import io.reactivex.rxkotlin.subscribeBy import javax.inject.Inject @@ -48,6 +49,7 @@ class UrlInitializer(private val url: String) : TabInitializer { /** * An initializer that displays the page set as the user's homepage preference. */ +@Reusable class HomePageInitializer @Inject constructor( private val userPreferences: UserPreferences, private val startPageInitializer: StartPageInitializer, @@ -69,6 +71,7 @@ class HomePageInitializer @Inject constructor( /** * An initializer that displays the start page. */ +@Reusable class StartPageInitializer @Inject constructor( homePageFactory: HomePageFactory, @DiskScheduler diskScheduler: Scheduler, @@ -78,6 +81,7 @@ class StartPageInitializer @Inject constructor( /** * An initializer that displays the bookmark page. */ +@Reusable class BookmarkPageInitializer @Inject constructor( bookmarkPageFactory: BookmarkPageFactory, @DiskScheduler diskScheduler: Scheduler, @@ -87,6 +91,7 @@ class BookmarkPageInitializer @Inject constructor( /** * An initializer that displays the download page. */ +@Reusable class DownloadPageInitializer @Inject constructor( downloadPageFactory: DownloadPageFactory, @DiskScheduler diskScheduler: Scheduler, @@ -96,6 +101,7 @@ class DownloadPageInitializer @Inject constructor( /** * An initializer that displays the history page. */ +@Reusable class HistoryPageInitializer @Inject constructor( historyPageFactory: HistoryPageFactory, @DiskScheduler diskScheduler: Scheduler, diff --git a/app/src/main/res/layout/bookmark_drawer.xml b/app/src/main/res/layout/bookmark_drawer.xml index 7572a4cfb..9ba6dd078 100644 --- a/app/src/main/res/layout/bookmark_drawer.xml +++ b/app/src/main/res/layout/bookmark_drawer.xml @@ -1,6 +1,5 @@ - - - - - + android:contentDescription="@string/action_back" + android:scaleType="center" + app:srcCompat="@drawable/ic_action_star" /> + android:textAppearance="?android:attr/textAppearanceLarge" /> + android:dividerHeight="0dp" /> - + android:contentDescription="@string/dialog_tools_title" + android:scaleType="center" + app:srcCompat="@drawable/ic_page_tools" /> - - - - + android:contentDescription="@string/action_add_bookmark" + android:scaleType="center" + app:srcCompat="@drawable/state_ic_bookmark" /> - - - - - - - + android:contentDescription="@string/reading_mode" + android:scaleType="center" + app:srcCompat="@drawable/ic_action_reading" /> diff --git a/app/src/main/res/layout/bookmark_list_item.xml b/app/src/main/res/layout/bookmark_list_item.xml index 874b39f9c..68fc20cd7 100644 --- a/app/src/main/res/layout/bookmark_list_item.xml +++ b/app/src/main/res/layout/bookmark_list_item.xml @@ -1,6 +1,5 @@ - - + android:layout_width="@dimen/material_grid_small_icon" + android:layout_height="@dimen/material_grid_small_icon" + android:layout_marginLeft="@dimen/material_grid_margin" + android:layout_marginRight="@dimen/material_grid_margin" + android:gravity="center_vertical" + android:contentDescription="@null" /> + android:textAppearance="?android:attr/textAppearanceListItemSmall" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/dialog_auth_request.xml b/app/src/main/res/layout/dialog_auth_request.xml index d82335a6c..b15ee32fb 100644 --- a/app/src/main/res/layout/dialog_auth_request.xml +++ b/app/src/main/res/layout/dialog_auth_request.xml @@ -1,6 +1,5 @@ - + android:maxLines="3" /> + android:inputType="textVisiblePassword" + android:maxLines="1" /> + android:maxLines="1" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/dialog_edit_bookmark.xml b/app/src/main/res/layout/dialog_edit_bookmark.xml index 19db5d239..e84b67a36 100644 --- a/app/src/main/res/layout/dialog_edit_bookmark.xml +++ b/app/src/main/res/layout/dialog_edit_bookmark.xml @@ -1,6 +1,5 @@ - + android:singleLine="true" /> + android:singleLine="true" /> - \ No newline at end of file + android:singleLine="true" /> + diff --git a/app/src/main/res/layout/dialog_list_item.xml b/app/src/main/res/layout/dialog_list_item.xml index 7297b5adb..7b86ab5d9 100644 --- a/app/src/main/res/layout/dialog_list_item.xml +++ b/app/src/main/res/layout/dialog_list_item.xml @@ -1,20 +1,19 @@ - + android:layout_width="@dimen/material_grid_small_icon" + android:layout_height="@dimen/material_grid_small_icon" + android:layout_marginLeft="@dimen/material_grid_margin" + android:layout_marginRight="@dimen/material_grid_margin" + android:contentDescription="@null" + android:gravity="center_vertical" /> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:textAppearance="?android:attr/textAppearanceListItemSmall" /> diff --git a/app/src/main/res/layout/dialog_manual_proxy.xml b/app/src/main/res/layout/dialog_manual_proxy.xml index cb7d32a9f..6d3a866ae 100644 --- a/app/src/main/res/layout/dialog_manual_proxy.xml +++ b/app/src/main/res/layout/dialog_manual_proxy.xml @@ -13,7 +13,7 @@ + android:inputType="text" + android:importantForAutofill="no" /> + android:inputType="number" + android:importantForAutofill="no" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/dialog_ssl_info.xml b/app/src/main/res/layout/dialog_ssl_info.xml new file mode 100644 index 000000000..cd776ed53 --- /dev/null +++ b/app/src/main/res/layout/dialog_ssl_info.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_dialog.xml b/app/src/main/res/layout/list_dialog.xml index 521830c69..41290377d 100644 --- a/app/src/main/res/layout/list_dialog.xml +++ b/app/src/main/res/layout/list_dialog.xml @@ -1,9 +1,8 @@ - + android:paddingBottom="@dimen/material_grid_unit" + android:textAppearance="@style/TextAppearance.AppCompat.Title" /> + android:overScrollMode="ifContentScrolls" /> diff --git a/app/src/main/res/layout/reading_view.xml b/app/src/main/res/layout/reading_view.xml index 43a0a797f..68ea5c8b7 100644 --- a/app/src/main/res/layout/reading_view.xml +++ b/app/src/main/res/layout/reading_view.xml @@ -23,13 +23,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:padding="20dp"> + android:padding="@dimen/material_grid_margin"> diff --git a/app/src/main/res/layout/search.xml b/app/src/main/res/layout/search.xml index 88186b596..fcae628e8 100644 --- a/app/src/main/res/layout/search.xml +++ b/app/src/main/res/layout/search.xml @@ -1,14 +1,13 @@ - + + + app:layout_constraintStart_toEndOf="@+id/search_ssl_status" + app:layout_constraintTop_toTopOf="parent" + app:layout_goneMarginLeft="8dp" /> - + app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/search_interface.xml b/app/src/main/res/layout/search_interface.xml index c9687fb61..478ec12b0 100644 --- a/app/src/main/res/layout/search_interface.xml +++ b/app/src/main/res/layout/search_interface.xml @@ -4,7 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/search_bar" android:layout_width="match_parent" - android:layout_height="@dimen/search_bar_height" + android:layout_height="@dimen/material_grid_touch_medium" android:background="?attr/colorPrimary" android:orientation="horizontal" android:visibility="gone" @@ -18,7 +18,7 @@ android:ellipsize="end" android:fontFamily="sans-serif" android:gravity="center_vertical" - android:paddingLeft="16dp" + android:paddingLeft="@dimen/material_grid_margin" android:textAppearance="?android:attr/textAppearanceListItem" android:textSize="22sp"/> diff --git a/app/src/main/res/layout/tab_drawer.xml b/app/src/main/res/layout/tab_drawer.xml index dfdfe7df7..21d656629 100644 --- a/app/src/main/res/layout/tab_drawer.xml +++ b/app/src/main/res/layout/tab_drawer.xml @@ -14,26 +14,19 @@ android:gravity="center_vertical" android:orientation="horizontal"> - - - - + android:contentDescription="@string/action_new_tab" + android:scaleType="center" + app:srcCompat="@drawable/ic_action_tabs" /> - - - - + android:contentDescription="@string/action_back" + android:scaleType="center" + app:srcCompat="@drawable/ic_action_back" /> - - - - + android:contentDescription="@string/action_homepage" + android:scaleType="center" + app:srcCompat="@drawable/ic_action_home" /> - + android:contentDescription="@string/action_forward" + android:scaleType="center" + app:srcCompat="@drawable/ic_action_forward" /> - - - - - - - + android:contentDescription="@string/action_new_tab" + android:scaleType="center" + app:srcCompat="@drawable/ic_action_plus" /> diff --git a/app/src/main/res/layout/tab_list_item.xml b/app/src/main/res/layout/tab_list_item.xml index a4b799c0c..02ef4c6ba 100644 --- a/app/src/main/res/layout/tab_list_item.xml +++ b/app/src/main/res/layout/tab_list_item.xml @@ -1,10 +1,9 @@ - + android:layout_width="@dimen/material_grid_small_icon" + android:layout_height="@dimen/material_grid_small_icon" + android:layout_marginLeft="@dimen/material_grid_margin" + android:layout_marginRight="@dimen/material_grid_margin" + android:contentDescription="@null" + android:gravity="center_vertical" /> + android:textAppearance="?android:attr/textAppearanceListItemSmall" /> - - - - + android:contentDescription="@string/close_tab" + android:scaleType="center" + app:srcCompat="@drawable/ic_action_delete" /> diff --git a/app/src/main/res/layout/tab_list_item_horizontal.xml b/app/src/main/res/layout/tab_list_item_horizontal.xml index 545e3a51e..efe496a1f 100644 --- a/app/src/main/res/layout/tab_list_item_horizontal.xml +++ b/app/src/main/res/layout/tab_list_item_horizontal.xml @@ -1,9 +1,8 @@ - + android:contentDescription="@null" + android:gravity="center_vertical" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> + app:srcCompat="@drawable/ic_action_delete" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/tab_strip.xml b/app/src/main/res/layout/tab_strip.xml index 7afd01fa1..77f55e396 100644 --- a/app/src/main/res/layout/tab_strip.xml +++ b/app/src/main/res/layout/tab_strip.xml @@ -3,14 +3,14 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="30dp" + android:layout_height="@dimen/desktop_tab_height" android:background="@color/black" tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> - @@ -17,31 +16,30 @@ android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"/> + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@id/tabs_toolbar_container" /> + app:progressColor="?attr/colorAccent" /> diff --git a/app/src/main/res/layout/toolbar_content.xml b/app/src/main/res/layout/toolbar_content.xml index e94450927..4e7c5d103 100644 --- a/app/src/main/res/layout/toolbar_content.xml +++ b/app/src/main/res/layout/toolbar_content.xml @@ -22,8 +22,8 @@ - + android:paddingStart="@dimen/material_grid_margin" + android:paddingTop="3dp" + android:paddingEnd="@dimen/material_grid_margin" + android:paddingBottom="3dp"> + android:layout_width="@dimen/material_grid_small_icon" + android:layout_height="@dimen/material_grid_small_icon" + android:contentDescription="@null" /> + android:paddingLeft="@dimen/material_grid_margin" + android:paddingRight="@dimen/material_grid_margin"> + android:textSize="18sp" /> + android:textColor="?attr/autoCompleteUrlColor" /> diff --git a/app/src/main/res/values-v16/styles.xml b/app/src/main/res/values-v16/styles.xml deleted file mode 100644 index 46a0c06ac..000000000 --- a/app/src/main/res/values-v16/styles.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 197485fdc..405597900 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,16 +1,26 @@ - - 48dp + 16dp + 8dp + + 24dp + + 36dp + 48dp + 56dp + 72dp 350dp 24dp - 8dp - 16dp + 300dp - 24dp + 56dp + 52dp + 56dp - 300dp + 175dp + 30dp + 2dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 497fb66fc..86c69bb71 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -329,4 +329,13 @@ Failed to load hosts from ad blocking source. + + + Issued by + Issued to + Issued on + Expires on + + + \'%1$s\' diff --git a/build.gradle b/build.gradle index 2afa0ee54..e0a2ce8c1 100644 --- a/build.gradle +++ b/build.gradle @@ -22,10 +22,6 @@ allprojects { ext { minSdkVersion = 19 - targetSdkVersion = 28 - buildToolsVersion = '28.0.3' - - versionName = '5.0.1' - versionCode_lite = 100 - versionCode_plus = 99 + targetSdkVersion = 29 + buildToolsVersion = '29.0.2' }