diff --git a/app/build.gradle b/app/build.gradle index 2861c61..30e665e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,7 @@ android { versionCode rootProject.ext.appVersionCode versionName rootProject.ext.appVersionName + "." + new Date().format('yyyyMMdd') testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - resConfigs "en", "fr" + resConfigs "en", "fr", "pt-rBR" } dexOptions { diff --git a/app/src/main/java/com/mercandalli/android/browser/main/ApplicationGraph.kt b/app/src/main/java/com/mercandalli/android/browser/main/ApplicationGraph.kt index 62e8bf0..62a7ef9 100644 --- a/app/src/main/java/com/mercandalli/android/browser/main/ApplicationGraph.kt +++ b/app/src/main/java/com/mercandalli/android/browser/main/ApplicationGraph.kt @@ -5,6 +5,7 @@ import android.content.Context import com.mercandalli.android.browser.ad_blocker.AdBlockerModule import com.mercandalli.android.browser.product.ProductModule import com.mercandalli.android.browser.remote_config.RemoteConfigModule +import com.mercandalli.android.browser.search_engine.SearchEngineModule import com.mercandalli.android.browser.theme.ThemeModule import com.mercandalli.android.browser.thread.MainThreadModule import com.mercandalli.android.browser.toast.ToastModule @@ -19,6 +20,7 @@ class ApplicationGraph( private val productManagerInternal by lazy { ProductModule().createProductManager() } private val mainThreadPostInternal by lazy { MainThreadModule().createMainThreadPost() } private val remoteConfigInternal by lazy { RemoteConfigModule().createRemoteConfig(mainThreadPostInternal) } + private val searchEngineManagerInternal by lazy { SearchEngineModule().createSearchEngineManager() } private val themeManagerInternal by lazy { ThemeModule(context).createThemeManager() } private val toastManagerInternal by lazy { ToastModule().createToastManager(context, mainThreadPostInternal) } private val updateManagerInternal by lazy { UpdateModule().createUpdateManager(context, versionManagerInternal) } @@ -46,6 +48,9 @@ class ApplicationGraph( @JvmStatic fun getRemoteConfig() = graph!!.remoteConfigInternal + @JvmStatic + fun getSearchEngineManager() = graph!!.searchEngineManagerInternal + @JvmStatic fun getThemeManager() = graph!!.themeManagerInternal diff --git a/app/src/main/java/com/mercandalli/android/browser/main/MainActivity.kt b/app/src/main/java/com/mercandalli/android/browser/main/MainActivity.kt index 84f18af..eb93ff2 100644 --- a/app/src/main/java/com/mercandalli/android/browser/main/MainActivity.kt +++ b/app/src/main/java/com/mercandalli/android/browser/main/MainActivity.kt @@ -12,7 +12,6 @@ import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu -import androidx.appcompat.widget.Toolbar import android.view.Gravity import android.view.KeyEvent import android.view.View @@ -32,7 +31,7 @@ import com.mercandalli.android.libs.monetization.MonetizationGraph class MainActivity : AppCompatActivity(), MainActivityContract.Screen { - private val toolbar: Toolbar by bind(R.id.activity_main_toolbar) + private val toolbar: View by bind(R.id.activity_main_toolbar) private val toolbarShadow: View by bind(R.id.activity_main_toolbar_shadow) private val webView: BrowserView by bind(R.id.activity_main_web_view) private val emptyView: View by bind(R.id.activity_main_empty_view) @@ -40,8 +39,7 @@ class MainActivity : AppCompatActivity(), MainActivityContract.Screen { private val progress: ProgressBar by bind(R.id.activity_main_progress) private val input: EditText by bind(R.id.activity_main_search) private val more: View by bind(R.id.activity_main_more) - private val fabClear: FloatingActionButton by bind(R.id.activity_main_fab_clear) - private val fabFullScreen: FloatingActionButton by bind(R.id.activity_main_fab_fullscreen) + private val fab: FloatingActionButton by bind(R.id.activity_main_fab_clear) private val browserWebViewListener = createBrowserWebViewListener() private val userAction = createUserAction() @@ -56,7 +54,6 @@ class MainActivity : AppCompatActivity(), MainActivityContract.Screen { return } setContentView(R.layout.activity_main) - setSupportActionBar(toolbar) more.setOnClickListener { showOverflowPopupMenu(more) } webView.browserWebViewListener = browserWebViewListener input.setOnEditorActionListener(createOnEditorActionListener()) @@ -65,13 +62,10 @@ class MainActivity : AppCompatActivity(), MainActivityContract.Screen { navigateHome() } - fabClear.setOnClickListener { - userAction.onFabClicked(false) + fab.setOnClickListener { + userAction.onFabClicked() } - fabFullScreen.setOnClickListener { - userAction.onFabClicked(true) - } - userAction.onCreate(savedInstanceState == null) + userAction.onCreate(savedInstanceState) } override fun onDestroy() { @@ -83,16 +77,18 @@ class MainActivity : AppCompatActivity(), MainActivityContract.Screen { userAction.onDestroy() } - override fun onSaveInstanceState(outState: Bundle?) { + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) if (forceDestroy) { return } + userAction.onSaveInstanceState(outState) webView.saveState(outState) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) + userAction.onRestoreInstanceState(savedInstanceState) webView.restoreState(savedInstanceState) } @@ -180,6 +176,15 @@ class MainActivity : AppCompatActivity(), MainActivityContract.Screen { window.statusBarColor = color } + @RequiresApi(api = Build.VERSION_CODES.M) + override fun setStatusBarDark(statusBarDark: Boolean) { + val flags = window.decorView.systemUiVisibility + window.decorView.systemUiVisibility = if (statusBarDark) + flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() + else + flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } + override fun setToolbarBackgroundColorRes(@ColorRes colorRes: Int) { val color = ContextCompat.getColor(this, colorRes) toolbar.setBackgroundColor(color) @@ -191,23 +196,14 @@ class MainActivity : AppCompatActivity(), MainActivityContract.Screen { emptyTextView.setTextColor(color) } - override fun showFabClear() { - if (!fabClear.isShown) { - fabClear.show() - } - fabFullScreen.hide() - } - - override fun showFabExpand() { - if (!fabFullScreen.isShown) { - fabFullScreen.show() + override fun showFab() { + if (!fab.isShown) { + fab.show() } - fabClear.hide() } override fun hideFab() { - fabClear.hide() - fabFullScreen.hide() + fab.hide() } override fun showWebView() { @@ -278,9 +274,11 @@ class MainActivity : AppCompatActivity(), MainActivityContract.Screen { private fun createUserAction(): MainActivityContract.UserAction { val themeManager = ApplicationGraph.getThemeManager() + val searchEngineManager = ApplicationGraph.getSearchEngineManager() return MainActivityPresenter( this, - themeManager + themeManager, + searchEngineManager ) } diff --git a/app/src/main/java/com/mercandalli/android/browser/main/MainActivityContract.kt b/app/src/main/java/com/mercandalli/android/browser/main/MainActivityContract.kt index 7886d8c..629bdd2 100644 --- a/app/src/main/java/com/mercandalli/android/browser/main/MainActivityContract.kt +++ b/app/src/main/java/com/mercandalli/android/browser/main/MainActivityContract.kt @@ -1,6 +1,7 @@ package com.mercandalli.android.browser.main import android.os.Build +import android.os.Bundle import androidx.annotation.ColorRes import androidx.annotation.RequiresApi @@ -8,10 +9,14 @@ internal class MainActivityContract { internal interface UserAction { - fun onCreate(firstActivityLaunch: Boolean) + fun onCreate(savedInstanceState: Bundle?) fun onDestroy() + fun onSaveInstanceState(outState: Bundle) + + fun onRestoreInstanceState(outState: Bundle) + fun onSearchPerformed(search: String) fun onHomeClicked() @@ -26,7 +31,7 @@ internal class MainActivityContract { fun onBackPressed(emptyViewVisible: Boolean) - fun onFabClicked(expand: Boolean) + fun onFabClicked() } internal interface Screen { @@ -62,13 +67,14 @@ internal class MainActivityContract { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) fun setStatusBarBackgroundColorRes(@ColorRes colorRes: Int) + @RequiresApi(api = Build.VERSION_CODES.M) + fun setStatusBarDark(statusBarDark: Boolean) + fun setToolbarBackgroundColorRes(@ColorRes colorRes: Int) fun setInputTextColorRes(@ColorRes colorRes: Int) - fun showFabClear() - - fun showFabExpand() + fun showFab() fun hideFab() diff --git a/app/src/main/java/com/mercandalli/android/browser/main/MainActivityPresenter.kt b/app/src/main/java/com/mercandalli/android/browser/main/MainActivityPresenter.kt index d23dc09..4ba26b8 100644 --- a/app/src/main/java/com/mercandalli/android/browser/main/MainActivityPresenter.kt +++ b/app/src/main/java/com/mercandalli/android/browser/main/MainActivityPresenter.kt @@ -1,25 +1,27 @@ package com.mercandalli.android.browser.main import android.os.Build +import android.os.Bundle +import com.mercandalli.android.browser.search_engine.SearchEngineManager import com.mercandalli.android.browser.theme.Theme import com.mercandalli.android.browser.theme.ThemeManager internal class MainActivityPresenter( private val screen: MainActivityContract.Screen, - private val themeManager: ThemeManager + private val themeManager: ThemeManager, + private val searchEngineManager: SearchEngineManager ) : MainActivityContract.UserAction { private val themeListener = createThemeListener() + private var webViewVisible = false - override fun onCreate(firstActivityLaunch: Boolean) { + override fun onCreate(savedInstanceState: Bundle?) { themeManager.registerThemeListener(themeListener) updateTheme() + val firstActivityLaunch = savedInstanceState == null if (firstActivityLaunch) { - screen.hideFab() - screen.hideWebView() - screen.showEmptyView() screen.showKeyboard() - screen.showToolbar() + setWebViewVisible(webViewVisible) } } @@ -27,35 +29,33 @@ internal class MainActivityPresenter( themeManager.unregisterThemeListener(themeListener) } + override fun onSaveInstanceState(outState: Bundle) { + outState.putBoolean("webViewVisible", webViewVisible) + } + + override fun onRestoreInstanceState(outState: Bundle) { + webViewVisible = outState.getBoolean("webViewVisible") + setWebViewVisible(webViewVisible) + } + override fun onSearchPerformed(search: String) { val url = searchToUrl(search) - screen.showLoader(0) screen.showUrl(url) screen.resetSearchInput() - screen.hideKeyboard() - screen.showFabExpand() - screen.showWebView() - screen.hideEmptyView() - screen.hideToolbar() + setWebViewVisible(true) } override fun onHomeClicked() { - screen.hideLoader() - screen.navigateHome() + setWebViewVisible(false) } override fun onClearDataClicked() { screen.clearData() screen.showClearDataMessage() - screen.hideLoader() - screen.navigateHome() - screen.hideWebView() - screen.showEmptyView() - screen.showToolbar() + setWebViewVisible(false) } override fun onSettingsClicked() { - screen.hideLoader() screen.navigateSettings() } @@ -79,27 +79,32 @@ internal class MainActivityPresenter( screen.back() } - override fun onFabClicked(expand: Boolean) { - screen.showToolbar() - if (expand) { - screen.showFabClear() - return - } + override fun onFabClicked() { screen.clearData() screen.showClearDataMessage() - screen.hideLoader() - screen.navigateHome() - screen.hideFab() - screen.hideWebView() - screen.showEmptyView() - screen.showKeyboard() + setWebViewVisible(false) } private fun searchToUrl(search: String): String { - return if (search.startsWith("https://") || search.startsWith("http://")) { - search + return searchEngineManager.createSearchUrl(search) + } + + private fun setWebViewVisible(visible: Boolean) { + webViewVisible = visible + if (visible) { + screen.showFab() + screen.hideToolbar() + screen.showWebView() + screen.hideEmptyView() + screen.showLoader(0) + screen.hideKeyboard() } else { - "https://www.google.fr/search?q=" + search.replace(" ", "+") + screen.hideFab() + screen.showToolbar() + screen.hideWebView() + screen.showEmptyView() + screen.hideLoader() + screen.showKeyboard() } } @@ -110,6 +115,9 @@ internal class MainActivityPresenter( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { screen.setStatusBarBackgroundColorRes(theme.statusBarBackgroundColorRes) } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + screen.setStatusBarDark(theme.statusBarDark) + } } private fun createThemeListener() = object : ThemeManager.ThemeListener { diff --git a/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngine.kt b/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngine.kt new file mode 100644 index 0000000..1e9251e --- /dev/null +++ b/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngine.kt @@ -0,0 +1,24 @@ +package com.mercandalli.android.browser.search_engine + +import androidx.annotation.StringDef + +data class SearchEngine( + @SearchEngineKey val searchEngineKey: String, + val name: String +) { + + companion object { + + @StringDef( + SEARCH_ENGINE_GOOGLE, + SEARCH_ENGINE_YOUTUBE, + SEARCH_ENGINE_DUCK_DUCK_GO + ) + @Retention(AnnotationRetention.SOURCE) + annotation class SearchEngineKey + + const val SEARCH_ENGINE_GOOGLE = "search-engine-google" + const val SEARCH_ENGINE_YOUTUBE = "search-engine-youtube" + const val SEARCH_ENGINE_DUCK_DUCK_GO = "search-engine-duck-duck-go" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngineManager.kt b/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngineManager.kt new file mode 100644 index 0000000..1e75681 --- /dev/null +++ b/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngineManager.kt @@ -0,0 +1,20 @@ +package com.mercandalli.android.browser.search_engine + +interface SearchEngineManager { + + fun isFeatureAvailable(): Boolean + + fun createSearchUrl( + searchInput: String, + @SearchEngine.Companion.SearchEngineKey searchEngineKey: String = getSearchEngineKey() + ): String + + @SearchEngine.Companion.SearchEngineKey + fun getSearchEngineKey(): String + + fun getSearchEngine(): SearchEngine + + fun setSearchEngineKey(@SearchEngine.Companion.SearchEngineKey searchEngineKey: String) + + fun getSearchEngines(): List +} \ No newline at end of file diff --git a/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngineManagerImpl.kt b/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngineManagerImpl.kt new file mode 100644 index 0000000..533a401 --- /dev/null +++ b/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngineManagerImpl.kt @@ -0,0 +1,69 @@ +package com.mercandalli.android.browser.search_engine + +import android.content.SharedPreferences +import com.mercandalli.android.browser.product.ProductManager +import com.mercandalli.android.browser.search_engine.SearchEngine.Companion.SEARCH_ENGINE_DUCK_DUCK_GO +import com.mercandalli.android.browser.search_engine.SearchEngine.Companion.SEARCH_ENGINE_GOOGLE +import com.mercandalli.android.browser.search_engine.SearchEngine.Companion.SEARCH_ENGINE_YOUTUBE + +class SearchEngineManagerImpl( + private val productManager: ProductManager +) : SearchEngineManager { + + @SearchEngine.Companion.SearchEngineKey + private var searchEngineKey = SEARCH_ENGINE_GOOGLE + + private val searchEngines = ArrayList() + + init { + searchEngines.add(SearchEngine(SEARCH_ENGINE_GOOGLE, "Google")) + searchEngines.add(SearchEngine(SEARCH_ENGINE_YOUTUBE, "YouTube")) + searchEngines.add(SearchEngine(SEARCH_ENGINE_DUCK_DUCK_GO, "DuckDuckGo")) + } + + override fun isFeatureAvailable() = productManager.isFullVersionAvailable() + + override fun createSearchUrl( + searchInput: String, + @SearchEngine.Companion.SearchEngineKey searchEngineKey: String + ): String { + if (searchInput.startsWith("https://") || searchInput.startsWith("http://")) { + return searchInput + } + return when (searchEngineKey) { + SEARCH_ENGINE_GOOGLE -> searchGoogle(searchInput) + SEARCH_ENGINE_YOUTUBE -> searchYouTube(searchInput) + SEARCH_ENGINE_DUCK_DUCK_GO -> searchDuckDuckGo(searchInput) + else -> searchGoogle(searchInput) + } + } + + @SearchEngine.Companion.SearchEngineKey + override fun getSearchEngineKey(): String { + return searchEngineKey + } + + override fun getSearchEngine(): SearchEngine { + for (searchEngine in searchEngines) { + if (searchEngineKey == searchEngine.searchEngineKey) { + return searchEngine + } + } + throw IllegalStateException("searchEngineKey: $searchEngineKey not found") + } + + override fun setSearchEngineKey(searchEngineKey: String) { + this.searchEngineKey = searchEngineKey + } + + override fun getSearchEngines() = ArrayList(searchEngines) + + private fun searchYouTube(searchInput: String) = + "https://www.youtube.com/results?utm_source=opensearch&search_query=" + searchInput.replace(" ", "+") + + private fun searchGoogle(searchInput: String) = + "https://www.google.fr/search?q=" + searchInput.replace(" ", "+") + + private fun searchDuckDuckGo(searchInput: String) = + "https://duckduckgo.com/?q=" + searchInput.replace(" ", "+") +} \ No newline at end of file diff --git a/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngineModule.kt b/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngineModule.kt new file mode 100644 index 0000000..b5de8de --- /dev/null +++ b/app/src/main/java/com/mercandalli/android/browser/search_engine/SearchEngineModule.kt @@ -0,0 +1,13 @@ +package com.mercandalli.android.browser.search_engine + +import com.mercandalli.android.browser.main.ApplicationGraph + +class SearchEngineModule { + + fun createSearchEngineManager(): SearchEngineManager { + val productManager = ApplicationGraph.getProductManager() + return SearchEngineManagerImpl( + productManager + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivity.kt b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivity.kt index 603e5db..c49732d 100644 --- a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivity.kt +++ b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivity.kt @@ -70,6 +70,15 @@ class SettingsActivity : AppCompatActivity(), window.statusBarColor = color } + @RequiresApi(api = Build.VERSION_CODES.M) + override fun setStatusBarDark(statusBarDark: Boolean) { + val flags = window.decorView.systemUiVisibility + window.decorView.systemUiVisibility = if (statusBarDark) + flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() + else + flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } + private fun createUserAction(): SettingsActivityContract.UserAction { val themeManager = ApplicationGraph.getThemeManager() return SettingsActivityPresenter( diff --git a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivityContract.kt b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivityContract.kt index 8ee5f8f..39aa522 100644 --- a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivityContract.kt +++ b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivityContract.kt @@ -18,6 +18,9 @@ interface SettingsActivityContract { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) fun setStatusBarBackgroundColorRes(@ColorRes colorRes: Int) + + @RequiresApi(api = Build.VERSION_CODES.M) + fun setStatusBarDark(statusBarDark: Boolean) } interface UserAction { diff --git a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivityPresenter.kt b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivityPresenter.kt index d347143..295d75c 100644 --- a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivityPresenter.kt +++ b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsActivityPresenter.kt @@ -34,6 +34,9 @@ class SettingsActivityPresenter( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { screen.setStatusBarBackgroundColorRes(theme.statusBarBackgroundColorRes) } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + screen.setStatusBarDark(theme.statusBarDark) + } } private fun createThemeListener() = object : ThemeManager.ThemeListener { diff --git a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsContract.kt b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsContract.kt index 4f206d9..61da956 100644 --- a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsContract.kt +++ b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsContract.kt @@ -1,6 +1,7 @@ package com.mercandalli.android.browser.settings import androidx.annotation.ColorRes +import com.mercandalli.android.browser.search_engine.SearchEngine import com.mercandalli.android.libs.monetization.in_app.InAppManager interface SettingsContract { @@ -29,6 +30,14 @@ interface SettingsContract { fun hideAdBlockerRow() + fun showSearchEngineUnlockRow() + + fun hideSearchEngineUnlockRow() + + fun showSearchEngineRow() + + fun hideSearchEngineRow() + fun showAdBlockSection() fun hideAdBlockSection() @@ -37,7 +46,19 @@ interface SettingsContract { fun hideAdBlockSectionLabel() + fun showSearchEngineSection() + + fun hideSearchEngineSection() + + fun showSearchEngineSectionLabel() + + fun hideSearchEngineSectionLabel() + fun setAdBlockerEnabled(enabled: Boolean) + + fun showSearchEngineSelection(searchEngines: List) + + fun displaySearchEngine(searchEngineName: String) } interface UserAction { @@ -50,6 +71,10 @@ interface SettingsContract { fun onAdBlockerCheckBoxCheckedChanged(isChecked: Boolean) - fun onUnlockAdsBlocker(activityContainer: InAppManager.ActivityContainer) + fun onAdBlockerUnlockRowClicked(activityContainer: InAppManager.ActivityContainer) + + fun onSearchEngineRowClicked() + + fun onSearchEngineUnlockRowClicked(activityContainer: InAppManager.ActivityContainer) } } \ No newline at end of file diff --git a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsPresenter.kt b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsPresenter.kt index 0bd55a8..a4b6984 100644 --- a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsPresenter.kt +++ b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsPresenter.kt @@ -6,6 +6,8 @@ import com.android.billingclient.api.SkuDetails import com.mercandalli.android.browser.ad_blocker.AdBlockerManager import com.mercandalli.android.browser.main.MainApplication import com.mercandalli.android.browser.product.ProductManager +import com.mercandalli.android.browser.search_engine.SearchEngine +import com.mercandalli.android.browser.search_engine.SearchEngineManager import com.mercandalli.android.browser.theme.Theme import com.mercandalli.android.browser.theme.ThemeManager import com.mercandalli.android.browser.version.VersionManager @@ -17,7 +19,8 @@ class SettingsPresenter( private val versionManager: VersionManager, private val inAppManager: InAppManager, private val adBlockerManager: AdBlockerManager, - private val productManager: ProductManager + private val productManager: ProductManager, + private val searchEngineManager: SearchEngineManager ) : SettingsContract.UserAction { private val themeListener = createThemeListener() @@ -28,7 +31,7 @@ class SettingsPresenter( inAppManager.registerListener(inAppManagerListener) updateTheme() setVersions() - syncAdBlockerRows() + syncRows() } override fun onDetached() { @@ -42,10 +45,30 @@ class SettingsPresenter( override fun onAdBlockerCheckBoxCheckedChanged(isChecked: Boolean) { adBlockerManager.setEnabled(isChecked) - syncAdBlockerRows() + syncRows() } - override fun onUnlockAdsBlocker(activityContainer: InAppManager.ActivityContainer) { + override fun onAdBlockerUnlockRowClicked(activityContainer: InAppManager.ActivityContainer) { + inAppManager.purchase( + activityContainer, + MainApplication.SKU_SUBSCRIPTION_FULL_VERSION, + BillingClient.SkuType.SUBS + ) + } + + override fun onSearchEngineRowClicked() { + screen.showSearchEngineSelection(searchEngineManager.getSearchEngines()) + + val searchEngineKey = searchEngineManager.getSearchEngineKey() + when (searchEngineKey) { + SearchEngine.SEARCH_ENGINE_GOOGLE -> searchEngineManager.setSearchEngineKey(SearchEngine.SEARCH_ENGINE_YOUTUBE) + SearchEngine.SEARCH_ENGINE_YOUTUBE -> searchEngineManager.setSearchEngineKey(SearchEngine.SEARCH_ENGINE_DUCK_DUCK_GO) + SearchEngine.SEARCH_ENGINE_DUCK_DUCK_GO -> searchEngineManager.setSearchEngineKey(SearchEngine.SEARCH_ENGINE_GOOGLE) + } + syncRows() + } + + override fun onSearchEngineUnlockRowClicked(activityContainer: InAppManager.ActivityContainer) { inAppManager.purchase( activityContainer, MainApplication.SKU_SUBSCRIPTION_FULL_VERSION, @@ -75,8 +98,9 @@ class SettingsPresenter( screen.setSectionColor(theme.cardBackgroundColorRes) } - private fun syncAdBlockerRows( + private fun syncRows( isAdBlockAvailable: Boolean = adBlockerManager.isFeatureAvailable(), + isSearchEngineAvailable: Boolean = searchEngineManager.isFeatureAvailable(), isSubscribeToFullVersion: Boolean = productManager.isSubscribeToFullVersion(), isEnabled: Boolean = adBlockerManager.isEnabled() ) { @@ -85,20 +109,30 @@ class SettingsPresenter( screen.showAdBlockSectionLabel() screen.hideAdBlockerUnlockRow() screen.showAdBlockerRow() + screen.hideSearchEngineUnlockRow() + screen.showSearchEngineRow() screen.setAdBlockerEnabled(isEnabled) + screen.displaySearchEngine(searchEngineManager.getSearchEngine().name) return } - if (!isAdBlockAvailable) { - screen.hideAdBlockerUnlockRow() + if (isSearchEngineAvailable) { + screen.showSearchEngineSection() + screen.showSearchEngineSectionLabel() + screen.showSearchEngineUnlockRow() + screen.hideSearchEngineRow() + } else { + screen.hideSearchEngineSection() + screen.hideSearchEngineSectionLabel() + } + if (isAdBlockAvailable) { + screen.showAdBlockSection() + screen.showAdBlockSectionLabel() + screen.showAdBlockerUnlockRow() screen.hideAdBlockerRow() + } else { screen.hideAdBlockSection() screen.hideAdBlockSectionLabel() - return } - screen.showAdBlockSection() - screen.showAdBlockSectionLabel() - screen.showAdBlockerUnlockRow() - screen.hideAdBlockerRow() } private fun createThemeListener() = object : ThemeManager.ThemeListener { @@ -113,7 +147,7 @@ class SettingsPresenter( } override fun onPurchasedChanged() { - syncAdBlockerRows() + syncRows() } } } \ No newline at end of file diff --git a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsView.kt b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsView.kt index f9b5dd5..fde4bb6 100644 --- a/app/src/main/java/com/mercandalli/android/browser/settings/SettingsView.kt +++ b/app/src/main/java/com/mercandalli/android/browser/settings/SettingsView.kt @@ -12,6 +12,7 @@ import android.widget.TextView import androidx.cardview.widget.CardView import com.mercandalli.android.browser.R import com.mercandalli.android.browser.main.ApplicationGraph +import com.mercandalli.android.browser.search_engine.SearchEngine import com.mercandalli.android.libs.monetization.MonetizationGraph import com.mercandalli.android.libs.monetization.in_app.InAppManager @@ -29,6 +30,17 @@ class SettingsView @JvmOverloads constructor( private val adBlockerLabel: TextView = view.findViewById(R.id.view_settings_ad_blocker_label) private val adBlockerSubLabel: TextView = view.findViewById(R.id.view_settings_ad_blocker_sublabel) private val adBlockerCheckBox: CheckBox = view.findViewById(R.id.view_settings_ad_blocker) + private val adBlockerUnlockLabel: TextView = view.findViewById(R.id.view_settings_ad_blocker_unlock_label) + private val adBlockerUnlockSubLabel: TextView = view.findViewById(R.id.view_settings_ad_blocker_unlock_sublabel) + + private val searchEngineRow: View = view.findViewById(R.id.view_settings_search_engine_row) + private val searchEngineUnlockRow: View = view.findViewById(R.id.view_settings_search_engine_unlock_row) + private val searchEngineSection: CardView = view.findViewById(R.id.view_settings_search_engine_section) + private val searchEngineSectionLabel: TextView = view.findViewById(R.id.view_settings_search_engine_section_label) + private val searchEngineLabel: TextView = view.findViewById(R.id.view_settings_search_engine_label) + private val searchEngineSubLabel: TextView = view.findViewById(R.id.view_settings_search_engine_sublabel) + private val searchEngineUnlockLabel: TextView = view.findViewById(R.id.view_settings_search_engine_unlock_label) + private val searchEngineUnlockSubLabel: TextView = view.findViewById(R.id.view_settings_search_engine_unlock_sublabel) private val themeRow: View = view.findViewById(R.id.view_settings_theme_row) private val themeSection: CardView = view.findViewById(R.id.view_settings_theme_section) @@ -65,7 +77,13 @@ class SettingsView @JvmOverloads constructor( userAction.onAdBlockerCheckBoxCheckedChanged(isChecked) } adBlockerUnlockRow.setOnClickListener { - userAction.onUnlockAdsBlocker(activityContainer!!) + userAction.onAdBlockerUnlockRowClicked(activityContainer!!) + } + searchEngineRow.setOnClickListener { + userAction.onSearchEngineRowClicked() + } + searchEngineUnlockRow.setOnClickListener { + userAction.onSearchEngineUnlockRowClicked(activityContainer!!) } } @@ -86,6 +104,7 @@ class SettingsView @JvmOverloads constructor( override fun setSectionColor(@ColorRes sectionColorRes: Int) { val sectionColor = ContextCompat.getColor(context, sectionColorRes) adBlockerSection.setCardBackgroundColor(sectionColor) + searchEngineSection.setCardBackgroundColor(sectionColor) themeSection.setCardBackgroundColor(sectionColor) aboutSection.setCardBackgroundColor(sectionColor) } @@ -93,6 +112,9 @@ class SettingsView @JvmOverloads constructor( override fun setTextPrimaryColorRes(@ColorRes textPrimaryColorRes: Int) { val textColor = ContextCompat.getColor(context, textPrimaryColorRes) adBlockerLabel.setTextColor(textColor) + adBlockerUnlockLabel.setTextColor(textColor) + searchEngineLabel.setTextColor(textColor) + searchEngineUnlockLabel.setTextColor(textColor) themeLabel.setTextColor(textColor) adBlockerCheckBox.setTextColor(textColor) themeLabel.setTextColor(textColor) @@ -105,9 +127,13 @@ class SettingsView @JvmOverloads constructor( override fun setTextSecondaryColorRes(@ColorRes textSecondaryColorRes: Int) { val textColor = ContextCompat.getColor(context, textSecondaryColorRes) adBlockerSectionLabel.setTextColor(textColor) + searchEngineSectionLabel.setTextColor(textColor) themeSectionLabel.setTextColor(textColor) aboutSectionLabel.setTextColor(textColor) adBlockerSubLabel.setTextColor(textColor) + adBlockerUnlockSubLabel.setTextColor(textColor) + searchEngineSubLabel.setTextColor(textColor) + searchEngineUnlockSubLabel.setTextColor(textColor) themeSubLabel.setTextColor(textColor) versionName.setTextColor(textColor) versionCode.setTextColor(textColor) @@ -142,6 +168,22 @@ class SettingsView @JvmOverloads constructor( adBlockerRow.visibility = GONE } + override fun showSearchEngineUnlockRow() { + searchEngineUnlockRow.visibility = VISIBLE + } + + override fun hideSearchEngineUnlockRow() { + searchEngineUnlockRow.visibility = GONE + } + + override fun showSearchEngineRow() { + searchEngineRow.visibility = VISIBLE + } + + override fun hideSearchEngineRow() { + searchEngineRow.visibility = GONE + } + override fun setAdBlockerEnabled(enabled: Boolean) { adBlockerCheckBox.isChecked = enabled } @@ -162,6 +204,30 @@ class SettingsView @JvmOverloads constructor( adBlockerSectionLabel.visibility = GONE } + override fun showSearchEngineSection() { + searchEngineSection.visibility = VISIBLE + } + + override fun hideSearchEngineSection() { + searchEngineSection.visibility = GONE + } + + override fun showSearchEngineSectionLabel() { + searchEngineSectionLabel.visibility = VISIBLE + } + + override fun hideSearchEngineSectionLabel() { + searchEngineSectionLabel.visibility = GONE + } + + override fun showSearchEngineSelection(searchEngines: List) { + // TODO + } + + override fun displaySearchEngine(searchEngineName: String) { + searchEngineSubLabel.text = context.resources.getString(R.string.view_settings_search_engine_sublabel, searchEngineName) + } + fun setActivityContainer(activityContainer: InAppManager.ActivityContainer) { this.activityContainer = activityContainer } @@ -172,7 +238,9 @@ class SettingsView @JvmOverloads constructor( override fun onDetached() {} override fun onDarkThemeCheckBoxCheckedChanged(isChecked: Boolean) {} override fun onAdBlockerCheckBoxCheckedChanged(isChecked: Boolean) {} - override fun onUnlockAdsBlocker(activityContainer: InAppManager.ActivityContainer) {} + override fun onAdBlockerUnlockRowClicked(activityContainer: InAppManager.ActivityContainer) {} + override fun onSearchEngineRowClicked() {} + override fun onSearchEngineUnlockRowClicked(activityContainer: InAppManager.ActivityContainer) {} } } else { val themeManager = ApplicationGraph.getThemeManager() @@ -180,13 +248,15 @@ class SettingsView @JvmOverloads constructor( val inAppManager = MonetizationGraph.getInAppManager() val adBlockerManager = ApplicationGraph.getAdBlockerManager() val productManager = ApplicationGraph.getProductManager() + val searchEngineManager = ApplicationGraph.getSearchEngineManager() SettingsPresenter( this, themeManager, versionManager, inAppManager, adBlockerManager, - productManager + productManager, + searchEngineManager ) } } \ No newline at end of file diff --git a/app/src/main/java/com/mercandalli/android/browser/theme/DarkTheme.kt b/app/src/main/java/com/mercandalli/android/browser/theme/DarkTheme.kt index 44fc5e2..a37550f 100644 --- a/app/src/main/java/com/mercandalli/android/browser/theme/DarkTheme.kt +++ b/app/src/main/java/com/mercandalli/android/browser/theme/DarkTheme.kt @@ -6,6 +6,7 @@ class DarkTheme : Theme( R.color.window_background_dark, R.color.window_settings_background_dark, R.color.status_bar_background_dark, + true, R.color.toolbar_background_dark, R.color.text_primary_color_dark, R.color.text_secondary_color_dark, diff --git a/app/src/main/java/com/mercandalli/android/browser/theme/LightTheme.kt b/app/src/main/java/com/mercandalli/android/browser/theme/LightTheme.kt index bea7c10..d050c53 100644 --- a/app/src/main/java/com/mercandalli/android/browser/theme/LightTheme.kt +++ b/app/src/main/java/com/mercandalli/android/browser/theme/LightTheme.kt @@ -7,6 +7,7 @@ class LightTheme : Theme( R.color.window_background_light, R.color.window_settings_background_light, R.color.status_bar_background_light, + false, R.color.toolbar_background_light, R.color.text_primary_color_light, R.color.text_secondary_color_light, diff --git a/app/src/main/java/com/mercandalli/android/browser/theme/Theme.kt b/app/src/main/java/com/mercandalli/android/browser/theme/Theme.kt index f0276fd..9aef45b 100644 --- a/app/src/main/java/com/mercandalli/android/browser/theme/Theme.kt +++ b/app/src/main/java/com/mercandalli/android/browser/theme/Theme.kt @@ -9,6 +9,7 @@ open class Theme( val windowSettingsBackgroundColorRes: Int, @ColorRes val statusBarBackgroundColorRes: Int, + val statusBarDark: Boolean, @ColorRes val toolbarBackgroundColorRes: Int, @ColorRes diff --git a/app/src/main/res/common/values-v21/style.xml b/app/src/main/res/common/values-v21/style.xml index 7089aed..c47cbd8 100644 --- a/app/src/main/res/common/values-v21/style.xml +++ b/app/src/main/res/common/values-v21/style.xml @@ -3,7 +3,7 @@ diff --git a/app/src/main/res/common/values/colors.xml b/app/src/main/res/common/values/colors.xml index 863fa5a..1a14d50 100644 --- a/app/src/main/res/common/values/colors.xml +++ b/app/src/main/res/common/values/colors.xml @@ -8,7 +8,7 @@ #F6F6F6 #F9F9F9 - @color/color_primary_darker + @android:color/white @android:color/white #303030 #808080 diff --git a/app/src/main/res/main/drawable/ic_fullscreen_exit_white_24dp.xml b/app/src/main/res/main/drawable/ic_fullscreen_exit_white_24dp.xml deleted file mode 100644 index a940aa1..0000000 --- a/app/src/main/res/main/drawable/ic_fullscreen_exit_white_24dp.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/main/layout/activity_main.xml b/app/src/main/res/main/layout/activity_main.xml index db7a5e3..88a5de9 100644 --- a/app/src/main/res/main/layout/activity_main.xml +++ b/app/src/main/res/main/layout/activity_main.xml @@ -4,60 +4,50 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" tools:context=".main.MainActivity"> - + android:layout_height="@dimen/toolbar_height"> - - - - - - - + android:layout_height="@dimen/toolbar_height" + android:layout_gravity="start|center_vertical" + android:layout_marginEnd="@dimen/default_space_5" + android:background="@android:color/transparent" + android:hint="Search" + android:imeOptions="actionSearch|flagNoExtractUi" + android:inputType="textNoSuggestions" + android:lines="1" + android:maxLines="1" + android:minLines="1" + android:paddingStart="@dimen/default_space_1_5" + android:paddingEnd="0dp" + android:singleLine="true" + android:textColor="@color/color_text_title_1" + android:textColorHint="#888888" + android:textSize="17sp" /> + + - + - - + + diff --git a/app/src/main/res/settings/layout/view_settings_ad_blocker.xml b/app/src/main/res/settings/layout/view_settings_ad_blocker.xml index 72f7278..b8b51b8 100644 --- a/app/src/main/res/settings/layout/view_settings_ad_blocker.xml +++ b/app/src/main/res/settings/layout/view_settings_ad_blocker.xml @@ -52,7 +52,7 @@ android:textSize="@dimen/text_size_xl" /> - - - - diff --git a/app/src/main/res/settings/layout/view_settings_search_engine.xml b/app/src/main/res/settings/layout/view_settings_search_engine.xml new file mode 100644 index 0000000..3da0d02 --- /dev/null +++ b/app/src/main/res/settings/layout/view_settings_search_engine.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/settings/values-fr/strings.xml b/app/src/main/res/settings/values-fr/strings.xml index 657e321..b2e8c6a 100644 --- a/app/src/main/res/settings/values-fr/strings.xml +++ b/app/src/main/res/settings/values-fr/strings.xml @@ -3,6 +3,18 @@ Paramètres + Ad blocker + Ad blocker + Try the ad blocker for free + Ad blocker + Enable ad blocker + + Moteur de recherche + Search engine + Try other search engine for free + Search engine + Set search engine. Current search engine is %1$s + Thème Thème Thème sombre diff --git a/app/src/main/res/settings/values/strings.xml b/app/src/main/res/settings/values/strings.xml index 2201c2f..32462cd 100644 --- a/app/src/main/res/settings/values/strings.xml +++ b/app/src/main/res/settings/values/strings.xml @@ -9,6 +9,13 @@ Ad blocker Enable ad blocker + Search engine + Search engine + Try other search engine for free + Search engine + Set search engine. + Set search engine. Current search engine is %1$s + Theme Theme Dark theme diff --git a/build.gradle b/build.gradle index 6f52943..b3563b7 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0-alpha09' + classpath 'com.android.tools.build:gradle:3.3.0-alpha10' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.0.1' @@ -45,8 +45,8 @@ task clean(type: Delete) { } ext { - appVersionCode = 1_00_12 - appVersionName = "1.00.12" + appVersionCode = 1_00_13 + appVersionName = "1.00.13" compileSdkVersion = 28 buildToolsVersion = "28.0.2" diff --git a/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingPageIndicator.kt b/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingPageIndicator.kt new file mode 100644 index 0000000..f2b75d1 --- /dev/null +++ b/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingPageIndicator.kt @@ -0,0 +1,49 @@ +package com.mercandalli.android.libs.monetization.on_boarding + +import androidx.viewpager.widget.ViewPager + +/** + * A PageIndicator is responsible to show an visual indicator on the total views + * number and the current visible view. + */ +interface OnBoardingPageIndicator : ViewPager.OnPageChangeListener { + + /** + * Bind the indicator to a ViewPager. + * + * @param view + */ + fun setViewPager(view: ViewPager) + + /** + * Bind the indicator to a ViewPager. + * + * @param view + * @param initialPosition + */ + fun setViewPager(view: ViewPager, initialPosition: Int) + + /** + * + * Set the current tutorial_page of both the ViewPager and indicator. + * + * + * This **must** be used if you need to set the tutorial_page before + * the views are drawn on screen (e.g., default start tutorial_page). + * + * @param item + */ + fun setCurrentItem(item: Int) + + /** + * Set a tutorial_page change listener which will receive forwarded events. + * + * @param listener + */ + fun setOnPageChangeListener(listener: ViewPager.OnPageChangeListener) + + /** + * Notify the indicator that the fragment list has changed. + */ + fun notifyDataSetChanged() +} \ No newline at end of file diff --git a/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingPageIndicatorView.kt b/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingPageIndicatorView.kt new file mode 100644 index 0000000..e93f109 --- /dev/null +++ b/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingPageIndicatorView.kt @@ -0,0 +1,472 @@ +package com.mercandalli.android.libs.monetization.on_boarding + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Paint.Style +import android.os.Parcel +import android.os.Parcelable +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration + +import com.mercandalli.android.libs.monetization.R + +import androidx.core.content.ContextCompat +import androidx.core.view.MotionEventCompat +import androidx.core.view.ViewConfigurationCompat +import androidx.viewpager.widget.ViewPager + +import android.graphics.Paint.ANTI_ALIAS_FLAG +import android.widget.LinearLayout.HORIZONTAL +import android.widget.LinearLayout.VERTICAL + +/** + * Draws circles (one for each view). The current view position is filled and + * others are only stroked. + */ +class OnBoardingPageIndicatorView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr), + OnBoardingPageIndicator { + + private var radiusInternal: Float = 0F + private val paintPageFill = Paint(ANTI_ALIAS_FLAG) + private val mPaintStroke = Paint(ANTI_ALIAS_FLAG) + private val mPaintFill = Paint(ANTI_ALIAS_FLAG) + private var viewPager: ViewPager? = null + private var listener: ViewPager.OnPageChangeListener? = null + private var mCurrentPage: Int = 0 + private var mSnapPage: Int = 0 + private var pageOffset: Float = 0.toFloat() + private var scrollState: Int = 0 + private var mOrientation: Int = 0 + private var mCentered: Boolean = false + private var snap: Boolean = false + + private val touchSlop: Int + private var mLastMotionX = -1f + private var mActivePointerId = INVALID_POINTER + private var mIsDragging: Boolean = false + + private var mNbItemRemovedDraw: Int = 0 + + var isCentered: Boolean + get() = mCentered + set(centered) { + mCentered = centered + invalidate() + } + + var pageColor: Int + get() = paintPageFill.color + set(pageColor) { + paintPageFill.color = pageColor + invalidate() + } + + var fillColor: Int + get() = mPaintFill.color + set(fillColor) { + mPaintFill.color = fillColor + invalidate() + } + + var orientation: Int + get() = mOrientation + set(orientation) = when (orientation) { + HORIZONTAL, VERTICAL -> { + mOrientation = orientation + requestLayout() + } + + else -> throw IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL.") + } + + var strokeColor: Int + get() = mPaintStroke.color + set(strokeColor) { + mPaintStroke.color = strokeColor + invalidate() + } + + var strokeWidth: Float + get() = mPaintStroke.strokeWidth + set(strokeWidth) { + mPaintStroke.strokeWidth = strokeWidth + invalidate() + } + + var radius: Float + get() = radiusInternal + set(radius) { + radiusInternal = radius + invalidate() + } + + var isSnap: Boolean + get() = snap + set(snap) { + this.snap = snap + invalidate() + } + + + init { + if (isInEditMode) { + touchSlop = 0 + } else { + // Load defaults from resources + val res = resources + val defaultPageColor = ContextCompat.getColor(context, R.color.carousel_circle_indicator_page_color) + val defaultFillColor = ContextCompat.getColor(context, R.color.carousel_circle_indicator_fill_color) + val defaultOrientation = res.getInteger(R.integer.carousel_circle_indicator_orientation) + val defaultStrokeColor = ContextCompat.getColor(context, R.color.carousel_circle_indicator_stroke_color) + val defaultStrokeWidth = res.getDimension(R.dimen.carousel_circle_indicator_stroke_width) + val defaultRadius = res.getDimension(R.dimen.carousel_circle_indicator_radius) + + // Retrieve styles attributes + val a = context.obtainStyledAttributes(attrs, R.styleable.OnBoardingPageIndicatorView, 0, 0) + + mCentered = a.getBoolean(R.styleable.OnBoardingPageIndicatorView_centered, true) + mOrientation = a.getInt(R.styleable.OnBoardingPageIndicatorView_android_orientation, defaultOrientation) + paintPageFill.style = Style.FILL + paintPageFill.color = a.getColor(R.styleable.OnBoardingPageIndicatorView_pageColor, defaultPageColor) + mPaintStroke.style = Style.STROKE + mPaintStroke.color = a.getColor(R.styleable.OnBoardingPageIndicatorView_strokeColor, defaultStrokeColor) + mPaintStroke.strokeWidth = a.getDimension(R.styleable.OnBoardingPageIndicatorView_strokeWidth, defaultStrokeWidth) + mPaintFill.style = Style.FILL + mPaintFill.color = a.getColor(R.styleable.OnBoardingPageIndicatorView_fillColor, defaultFillColor) + radiusInternal = a.getDimension(R.styleable.OnBoardingPageIndicatorView_radius, defaultRadius) + snap = a.getBoolean(R.styleable.OnBoardingPageIndicatorView_snap, false) + + val background = a.getDrawable(R.styleable.OnBoardingPageIndicatorView_android_background) + if (background != null) { + setBackgroundDrawable(background) + } + + a.recycle() + + val configuration = ViewConfiguration.get(context) + touchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration) + } + } + + fun setNbItemRemovedTodraw(nbItemRemoved: Int) { + mNbItemRemovedDraw = nbItemRemoved + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + if (viewPager == null) { + return + } + val count = viewPager!!.adapter!!.count - mNbItemRemovedDraw + if (count == 0) { + return + } + + val longSize: Int + val longPaddingBefore: Int + val longPaddingAfter: Int + val shortPaddingBefore: Int + if (mOrientation == HORIZONTAL) { + longSize = width + longPaddingBefore = paddingLeft + longPaddingAfter = paddingRight + shortPaddingBefore = paddingTop + } else { + longSize = height + longPaddingBefore = paddingTop + longPaddingAfter = paddingBottom + shortPaddingBefore = paddingLeft + } + + val fiveRadius = radiusInternal * 5 + val shortOffset = shortPaddingBefore + radiusInternal + var longOffset = longPaddingBefore + radiusInternal + if (mCentered) { + longOffset += (longSize - longPaddingBefore - longPaddingAfter) / 2.0f - count * fiveRadius / 2.0f + } + + var dX: Float + var dY: Float + + var pageFillRadius = radiusInternal + if (mPaintStroke.strokeWidth > 0) { + pageFillRadius -= mPaintStroke.strokeWidth / 2.0f + } + + //Draw stroked circles + for (iLoop in 0 until count) { + val drawLong = longOffset + iLoop * fiveRadius + if (mOrientation == HORIZONTAL) { + dX = drawLong + dY = shortOffset + } else { + dX = shortOffset + dY = drawLong + } + // Only paint fill if not completely transparent + if (paintPageFill.alpha > 0) { + canvas.drawCircle(dX, dY, pageFillRadius, paintPageFill) + } + + // Only paint stroke if a stroke width was non-zero + if (java.lang.Float.compare(pageFillRadius, radiusInternal) != 0) { + canvas.drawCircle(dX, dY, radiusInternal, mPaintStroke) + } + } + + // Draw the filled circle according to the current scroll + var cx = (if (snap) mSnapPage else mCurrentPage) * fiveRadius + if (!snap) { + cx += pageOffset * fiveRadius + } + if (mOrientation == HORIZONTAL) { + dX = longOffset + cx + dY = shortOffset + } else { + dX = shortOffset + dY = longOffset + cx + } + canvas.drawCircle(dX, dY, radiusInternal, mPaintFill) + } + + override fun onTouchEvent(ev: MotionEvent): Boolean { + if (super.onTouchEvent(ev)) { + return true + } + if (viewPager == null || viewPager!!.adapter!!.count == 0) { + return false + } + + val action = ev.action and MotionEventCompat.ACTION_MASK + when (action) { + MotionEvent.ACTION_DOWN -> { + mActivePointerId = MotionEventCompat.getPointerId(ev, 0) + mLastMotionX = ev.x + } + + MotionEvent.ACTION_MOVE -> { + val activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId) + val x = MotionEventCompat.getX(ev, activePointerIndex) + val deltaX = x - mLastMotionX + + if (!mIsDragging && Math.abs(deltaX) > touchSlop) { + mIsDragging = true + } + + if (mIsDragging) { + mLastMotionX = x + if (viewPager!!.isFakeDragging || viewPager!!.beginFakeDrag()) { + viewPager!!.fakeDragBy(deltaX) + } + } + } + + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + if (!mIsDragging) { + val count = viewPager!!.adapter!!.count + val width = width + val halfWidth = width / 2f + val sixthWidth = width / 6f + + if (mCurrentPage > 0 && ev.x < halfWidth - sixthWidth) { + if (action != MotionEvent.ACTION_CANCEL) { + viewPager!!.currentItem = mCurrentPage - 1 + } + return true + } else if (mCurrentPage < count - 1 && ev.x > halfWidth + sixthWidth) { + if (action != MotionEvent.ACTION_CANCEL) { + viewPager!!.currentItem = mCurrentPage + 1 + } + return true + } + } + + mIsDragging = false + mActivePointerId = INVALID_POINTER + if (viewPager!!.isFakeDragging) viewPager!!.endFakeDrag() + } + + MotionEventCompat.ACTION_POINTER_DOWN -> { + val index = MotionEventCompat.getActionIndex(ev) + mLastMotionX = MotionEventCompat.getX(ev, index) + mActivePointerId = MotionEventCompat.getPointerId(ev, index) + } + + MotionEventCompat.ACTION_POINTER_UP -> { + val pointerIndex = MotionEventCompat.getActionIndex(ev) + val pointerId = MotionEventCompat.getPointerId(ev, pointerIndex) + if (pointerId == mActivePointerId) { + val newPointerIndex = if (pointerIndex == 0) 1 else 0 + mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex) + } + mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId)) + } + } + + return true + } + + override fun setViewPager(view: ViewPager) { + if (viewPager === view) { + return + } + if (viewPager != null) { + viewPager!!.setOnPageChangeListener(null) + } + if (view.adapter == null) { + throw IllegalStateException("ViewPager does not have adapter instance.") + } + viewPager = view + viewPager!!.setOnPageChangeListener(this) + invalidate() + } + + override fun setViewPager(view: ViewPager, initialPosition: Int) { + setViewPager(view) + setCurrentItem(initialPosition) + } + + override fun setCurrentItem(item: Int) { + if (viewPager == null) { + throw IllegalStateException("ViewPager has not been bound.") + } + viewPager!!.currentItem = item + mCurrentPage = item + invalidate() + } + + override fun notifyDataSetChanged() { + invalidate() + } + + override fun onPageScrollStateChanged(state: Int) { + scrollState = state + + if (listener != null) { + listener!!.onPageScrollStateChanged(state) + } + } + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + mCurrentPage = position + pageOffset = positionOffset + invalidate() + + if (listener != null) { + listener!!.onPageScrolled(position, positionOffset, positionOffsetPixels) + } + } + + override fun onPageSelected(position: Int) { + if (snap || scrollState == ViewPager.SCROLL_STATE_IDLE) { + mCurrentPage = position + mSnapPage = position + invalidate() + } + + if (listener != null) { + listener!!.onPageSelected(position) + } + } + + override fun setOnPageChangeListener(listener: ViewPager.OnPageChangeListener) { + this.listener = listener + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + if (mOrientation == HORIZONTAL) { + setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec)) + } else { + setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec)) + } + } + + /** + * Determines the width of this view + * + * @param measureSpec A measureSpec packed into an int + * @return The width of the view, honoring constraints from measureSpec + */ + private fun measureLong(measureSpec: Int): Int { + var result: Int + val specMode = View.MeasureSpec.getMode(measureSpec) + val specSize = View.MeasureSpec.getSize(measureSpec) + + if (specMode == View.MeasureSpec.EXACTLY || viewPager == null) { + //We were told how big to be + result = specSize + } else { + //Calculate the width according the views count + val count = viewPager!!.adapter!!.count + result = (paddingLeft.toFloat() + paddingRight.toFloat() + + count.toFloat() * 2f * radiusInternal + (count - 1) * radiusInternal + 1f).toInt() + //Respect AT_MOST value if that was what is called for by measureSpec + if (specMode == View.MeasureSpec.AT_MOST) { + result = Math.min(result, specSize) + } + } + return result + } + + /** + * Determines the height of this view + * + * @param measureSpec A measureSpec packed into an int + * @return The height of the view, honoring constraints from measureSpec + */ + private fun measureShort(measureSpec: Int): Int { + var result: Int + val specMode = View.MeasureSpec.getMode(measureSpec) + val specSize = View.MeasureSpec.getSize(measureSpec) + + if (specMode == View.MeasureSpec.EXACTLY) { + //We were told how big to be + result = specSize + } else { + //Measure the height + result = (2 * radiusInternal + paddingTop.toFloat() + paddingBottom.toFloat() + 1f).toInt() + //Respect AT_MOST value if that was what is called for by measureSpec + if (specMode == View.MeasureSpec.AT_MOST) { + result = Math.min(result, specSize) + } + } + return result + } + + public override fun onRestoreInstanceState(state: Parcelable) { + val savedState = state as SavedState + super.onRestoreInstanceState(savedState.superState) + mCurrentPage = savedState.currentPage + mSnapPage = savedState.currentPage + requestLayout() + } + + public override fun onSaveInstanceState(): Parcelable? { + val superState = super.onSaveInstanceState() + val savedState = SavedState(superState) + savedState.currentPage = mCurrentPage + return savedState + } + + internal class SavedState : View.BaseSavedState { + var currentPage: Int = 0 + + constructor(superState: Parcelable) : super(superState) + + override fun writeToParcel(dest: Parcel, flags: Int) { + super.writeToParcel(dest, flags) + dest.writeInt(currentPage) + } + } + + companion object { + + private const val INVALID_POINTER = -1 + } +} \ No newline at end of file diff --git a/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingView.kt b/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingView.kt index 7c4994b..8ac0028 100644 --- a/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingView.kt +++ b/monetization/src/main/java/com/mercandalli/android/libs/monetization/on_boarding/OnBoardingView.kt @@ -37,6 +37,7 @@ class OnBoardingView @JvmOverloads constructor( private val storeBuy: View = view.findViewById(R.id.view_on_boarding_store_buy) private val storeSkip: View = view.findViewById(R.id.view_on_boarding_store_skip) private val next: View = view.findViewById(R.id.view_on_boarding_next) + private val indicatorOnBoarding: OnBoardingPageIndicator = view.findViewById(R.id.view_on_boarding_indicator) private val onPageChangeListener = createOnPageChangeListener() private val pages = SparseArray() private val adapter = createPagerAdapter() @@ -57,6 +58,7 @@ class OnBoardingView @JvmOverloads constructor( storeSkip.setOnClickListener { userAction.onStoreSkipClicked() } + indicatorOnBoarding.setViewPager(viewPager) } override fun onAttachedToWindow() { diff --git a/monetization/src/main/res/on_boarding/layout-land/view_on_boarding.xml b/monetization/src/main/res/on_boarding/layout-land/view_on_boarding.xml index 989d272..609c3c2 100644 --- a/monetization/src/main/res/on_boarding/layout-land/view_on_boarding.xml +++ b/monetization/src/main/res/on_boarding/layout-land/view_on_boarding.xml @@ -11,18 +11,31 @@ android:background="@color/on_boarding_view_bg" android:orientation="vertical"> - + android:layout_height="wrap_content"> + + + + + + + @@ -37,7 +37,6 @@ - + + + G - Browser + Navegação privada + • Apague seu histórico\n• Transmissão de vídeo\n• Streaming de música + Tema escuro + Página negra do Google + Versão completa + • Bloqueador de anúncios\n• Set Search engine + PRÓXIMO + COMECE O TESTE GRÁTIS + Use versão limitada + diff --git a/monetization/src/main/res/on_boarding/values/color.xml b/monetization/src/main/res/on_boarding/values/color.xml index 755105f..48ed1a4 100644 --- a/monetization/src/main/res/on_boarding/values/color.xml +++ b/monetization/src/main/res/on_boarding/values/color.xml @@ -4,4 +4,8 @@ @android:color/white #1565C0 #0D47A1 + + #55000000 + #22000000 + #22000000 \ No newline at end of file diff --git a/monetization/src/main/res/on_boarding/values/dimens.xml b/monetization/src/main/res/on_boarding/values/dimens.xml index 0c11d5c..4225d86 100644 --- a/monetization/src/main/res/on_boarding/values/dimens.xml +++ b/monetization/src/main/res/on_boarding/values/dimens.xml @@ -3,4 +3,8 @@ 10dp 10dp 34dp + + 0 + 0dp + 3dp \ No newline at end of file diff --git a/monetization/src/main/res/on_boarding/values/on_boarding_page_indicator_view_attrs.xml b/monetization/src/main/res/on_boarding/values/on_boarding_page_indicator_view_attrs.xml new file mode 100644 index 0000000..5e912d3 --- /dev/null +++ b/monetization/src/main/res/on_boarding/values/on_boarding_page_indicator_view_attrs.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/monetization/src/main/res/on_boarding/values/on_boarding_thumbnail_view_attrs.xml b/monetization/src/main/res/on_boarding/values/on_boarding_thumbnail_view_attrs.xml index 9b1b5ae..7071efc 100644 --- a/monetization/src/main/res/on_boarding/values/on_boarding_thumbnail_view_attrs.xml +++ b/monetization/src/main/res/on_boarding/values/on_boarding_thumbnail_view_attrs.xml @@ -1,8 +1,10 @@ + + \ No newline at end of file diff --git a/monetization/src/main/res/on_boarding/values/strings.xml b/monetization/src/main/res/on_boarding/values/strings.xml index cc62c4e..1a53c61 100644 --- a/monetization/src/main/res/on_boarding/values/strings.xml +++ b/monetization/src/main/res/on_boarding/values/strings.xml @@ -1,11 +1,12 @@ + G - Browser Private navigation • Erase your historic\n• Video streaming\n• Music streaming Dark theme Google page in black Full version - • Ad blocker + • Ad blocker\n• Set Search engine NEXT START FREE TRIAL - Use limited version without ad blocker + Use limited version