diff --git a/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchUrlConverterImpl.kt b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchUrlConverterImpl.kt new file mode 100644 index 000000000000..15f4c86f42c2 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchUrlConverterImpl.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.generalsettings.showonapplaunch + +import android.net.Uri +import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore + +class ShowOnAppLaunchUrlConverterImpl : UrlConverter { + + override fun convertUrl(url: String?): String { + if (url.isNullOrBlank()) return ShowOnAppLaunchOptionDataStore.DEFAULT_SPECIFIC_PAGE_URL + + val uri = Uri.parse(url.trim()) + + val convertedUri = if (uri.scheme == null) { + Uri.Builder().scheme("http").authority(uri.path?.lowercase()) + } else { + uri.buildUpon() + .scheme(uri.scheme?.lowercase()) + .authority(uri.authority?.lowercase()) + } + .apply { + query(uri.query) + fragment(uri.fragment) + } + .build() + .toString() + + return Uri.decode(convertedUri) + } +} diff --git a/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchViewModel.kt b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchViewModel.kt index 7b1a3d6aac09..dcdc4c8616d9 100644 --- a/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchViewModel.kt @@ -37,6 +37,7 @@ import timber.log.Timber class ShowOnAppLaunchViewModel @Inject constructor( private val dispatcherProvider: DispatcherProvider, private val showOnAppLaunchOptionDataStore: ShowOnAppLaunchOptionDataStore, + private val urlConverter: UrlConverter, ) : ViewModel() { data class ViewState( @@ -71,7 +72,8 @@ class ShowOnAppLaunchViewModel @Inject constructor( fun setSpecificPageUrl(url: String) { Timber.i("Setting specific page url to $url") viewModelScope.launch(dispatcherProvider.io()) { - showOnAppLaunchOptionDataStore.setSpecificPageUrl(url) + val convertedUrl = urlConverter.convertUrl(url) + showOnAppLaunchOptionDataStore.setSpecificPageUrl(convertedUrl) } } } diff --git a/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/UrlConverter.kt b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/UrlConverter.kt new file mode 100644 index 000000000000..87703ab36a73 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/UrlConverter.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.generalsettings.showonapplaunch + +interface UrlConverter { + + fun convertUrl(url: String?): String +} diff --git a/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/store/ShowOnAppLaunchDataStoreModule.kt b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/store/ShowOnAppLaunchDataStoreModule.kt index 6ad3493c006a..291efa1fe04a 100644 --- a/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/store/ShowOnAppLaunchDataStoreModule.kt +++ b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/store/ShowOnAppLaunchDataStoreModule.kt @@ -20,6 +20,8 @@ import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore +import com.duckduckgo.app.generalsettings.showonapplaunch.ShowOnAppLaunchUrlConverterImpl +import com.duckduckgo.app.generalsettings.showonapplaunch.UrlConverter import com.duckduckgo.di.scopes.AppScope import com.squareup.anvil.annotations.ContributesTo import dagger.Module @@ -37,6 +39,9 @@ object ShowOnAppLaunchDataStoreModule { @Provides @ShowOnAppLaunch fun showOnAppLaunchDataStore(context: Context): DataStore = context.showOnAppLaunchDataStore + + @Provides + fun showOnAppLaunchUrlConverter(): UrlConverter = ShowOnAppLaunchUrlConverterImpl() } @Qualifier diff --git a/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/store/ShowOnAppLaunchOptionDataStore.kt b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/store/ShowOnAppLaunchOptionDataStore.kt index 25fae184a2b7..4195b6ec49e3 100644 --- a/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/store/ShowOnAppLaunchOptionDataStore.kt +++ b/app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/store/ShowOnAppLaunchOptionDataStore.kt @@ -25,6 +25,7 @@ import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchO import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.LastOpenedTab import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.NewTabPage import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.SpecificPage +import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore.Companion.DEFAULT_SPECIFIC_PAGE_URL import com.duckduckgo.di.scopes.AppScope import com.squareup.anvil.annotations.ContributesBinding import javax.inject.Inject @@ -37,6 +38,10 @@ interface ShowOnAppLaunchOptionDataStore { suspend fun setShowOnAppLaunchOption(showOnAppLaunchOption: ShowOnAppLaunchOption) suspend fun setSpecificPageUrl(url: String) + + companion object { + const val DEFAULT_SPECIFIC_PAGE_URL = "https://duckduckgo.com" + } } @ContributesBinding(AppScope::class) @@ -79,7 +84,6 @@ class ShowOnAppLaunchOptionPrefsDataStore @Inject constructor( } companion object { - private const val DEFAULT_SPECIFIC_PAGE_URL = "duckduckgo.com" private const val KEY_SHOW_ON_APP_LAUNCH_OPTION = "SHOW_ON_APP_LAUNCH_OPTION" private const val KEY_SHOW_ON_APP_LAUNCH_SPECIFIC_PAGE_URL = "SHOW_ON_APP_LAUNCH_SPECIFIC_PAGE_URL" } diff --git a/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchUrlConverterImplTest.kt b/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchUrlConverterImplTest.kt new file mode 100644 index 000000000000..ed3109a79593 --- /dev/null +++ b/app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchUrlConverterImplTest.kt @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.generalsettings.showonapplaunch + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore.Companion.DEFAULT_SPECIFIC_PAGE_URL +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ShowOnAppLaunchUrlConverterImplTest { + + private val urlConverter = ShowOnAppLaunchUrlConverterImpl() + + @Test + fun whenUrlIsNullThenShouldReturnDefaultUrl() { + val result = urlConverter.convertUrl(null) + assertEquals(DEFAULT_SPECIFIC_PAGE_URL, result) + } + + @Test + fun whenUrlIsEmptyThenShouldReturnDefaultUrl() { + val result = urlConverter.convertUrl("") + assertEquals(DEFAULT_SPECIFIC_PAGE_URL, result) + } + + @Test + fun whenUrlIsBlankThenShouldReturnDefaultUrl() { + val result = urlConverter.convertUrl(" ") + assertEquals(DEFAULT_SPECIFIC_PAGE_URL, result) + } + + @Test + fun whenUrlHasNoSchemeThenHttpSchemeIsAdded() { + val result = urlConverter.convertUrl("www.example.com") + assertEquals("http://www.example.com", result) + } + + @Test + fun whenUrlHasNoSchemeAndSubdomainThenHttpSchemeIsAdded() { + val result = urlConverter.convertUrl("example.com") + assertEquals("http://example.com", result) + } + + @Test + fun whenUrlHasASchemeThenShouldReturnTheSameUrl() { + val result = urlConverter.convertUrl("https://www.example.com") + assertEquals("https://www.example.com", result) + } + + @Test + fun whenUrlHasDifferentSchemeThenShouldReturnTheSameUrl() { + val result = urlConverter.convertUrl("ftp://www.example.com") + assertEquals("ftp://www.example.com", result) + } + + @Test + fun whenUrlHasSpecialCharactersThenShouldReturnTheSameUrl() { + val result = urlConverter.convertUrl("https://www.example.com/path?query=param&another=param") + assertEquals("https://www.example.com/path?query=param&another=param", result) + } + + @Test + fun whenUrlHasPortThenShouldReturnTheSameUrl() { + val result = urlConverter.convertUrl("https://www.example.com:8080") + assertEquals("https://www.example.com:8080", result) + } + + @Test + fun whenUrlHasPathAndQueryParametersThenShouldReturnTheSameUrl() { + val result = urlConverter.convertUrl("https://www.example.com/path/to/resource?query=param") + assertEquals("https://www.example.com/path/to/resource?query=param", result) + } + + @Test + fun whenUrlHasUppercaseProtocolThenShouldLowercaseProtocol() { + val result = urlConverter.convertUrl("HTTPS://www.example.com") + assertEquals("https://www.example.com", result) + } + + @Test + fun whenUrlHasUppercaseSubdomainThenShouldLowercaseSubdomain() { + val result = urlConverter.convertUrl("https://WWW.example.com") + assertEquals("https://www.example.com", result) + } + + @Test + fun whenUrlHasUppercaseDomainThenShouldLowercaseDomain() { + val result = urlConverter.convertUrl("https://www.EXAMPLE.com") + assertEquals("https://www.example.com", result) + } + + @Test + fun whenUrlHasUppercaseTopLevelDomainThenShouldLowercaseTopLevelDomain() { + val result = urlConverter.convertUrl("https://www.example.COM") + assertEquals("https://www.example.com", result) + } + + @Test + fun whenUrlHasMixedCaseThenOnlyProtocolSubdomainDomainAndTldAreLowercased() { + val result = urlConverter.convertUrl("HTTPS://WWW.EXAMPLE.COM/Path?Query=Param#Fragment") + assertEquals("https://www.example.com/Path?Query=Param#Fragment", result) + } + + @Test + fun whenUrlIsNotAValidUrlReturnsInvalidUrlWithHttpScheme() { + val result = urlConverter.convertUrl("example") + assertEquals("http://example", result) + } +}