Skip to content

Commit

Permalink
Merge pull request feelfreelinux#165 from otwarty-wykop-mobilny/2fa
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuszkwiecinski committed Jan 6, 2022
2 parents c4f2e08 + 31b784b commit 1a3de98
Show file tree
Hide file tree
Showing 64 changed files with 908 additions and 105 deletions.
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,21 @@ android {
signingConfig signingConfigs.getByName("debug")
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
resValue "bool", "disable_firebase_analytics", "true"
}
named("release") {
minifyEnabled true
signingConfig signingConfigs.getByName("release")
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
firebaseCrashlytics.mappingFileUploadEnabled findProperty("uploadMapping") == "true"
resValue "bool", "disable_firebase_analytics", "false"
}
register("releaseTest") {
initWith getByName("release")
signingConfig signingConfigs.getByName("debug")
testProguardFile "proguard-rules-test.pro"
firebaseCrashlytics.mappingFileUploadEnabled false
resValue "bool", "disable_firebase_analytics", "true"
}
}

Expand Down Expand Up @@ -123,6 +126,7 @@ dependencies {
implementation(projects.ui.search.android)
implementation(projects.ui.settings.android)
implementation(projects.ui.notifications.android)
implementation(projects.ui.twoFactor.android)
implementation(projects.domain)
implementation(libs.recyclerview.core)
implementation(libs.appcompat.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withText
import io.github.wykopmobilny.tests.base.Page
import io.github.wykopmobilny.utils.waitVisible
import org.hamcrest.Matchers.startsWith

object AboutDialog : Page {

private val appInfo = withText(startsWith("Wykop Mobilny"))
private val appInfo = withText(startsWith("Wypok"))

fun tapAppInfo() {
onView(appInfo).perform(click())
onView(appInfo).waitVisible().perform(click())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import io.github.wykopmobilny.utils.waitVisible

object AppearanceSettingsPage {

fun assertVisible() {
onView(withText("Ustawienia")).check(matches(isDisplayed()))
onView(withText("Ustawienia")).waitVisible().check(matches(isDisplayed()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package io.github.wykopmobilny.tests.pages

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isSelected
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withText
import io.github.wykopmobilny.R
import io.github.wykopmobilny.utils.waitNotVisible
import io.github.wykopmobilny.utils.waitVisible
import org.hamcrest.CoreMatchers.allOf

object BlacklistPage {
Expand All @@ -24,46 +24,46 @@ object BlacklistPage {
)

fun tapUsersTab() {
onView(usersTab).perform(click())
onView(usersTab).waitVisible().perform(click())
}

fun tapTagsTab() {
onView(tagsTab).perform(click())
onView(tagsTab).waitVisible().perform(click())
}

fun tapUnblockTag(tag: String) {
onView(lockIcon(tag)).perform(click())
onView(lockIcon(tag)).waitVisible().perform(click())
}

fun tapUnblockUser(user: String) {
onView(lockIcon(user)).perform(click())
onView(lockIcon(user)).waitVisible().perform(click())
}

fun tapImportButton() {
onView(withText("Zaimportuj")).perform(click())
onView(withText("Zaimportuj")).waitVisible().perform(click())
}

fun assertVisible() {
onView(withText("Zarządzaj czarną listą")).check(matches(isDisplayed()))
onView(withText("Zarządzaj czarną listą")).waitVisible()
}

fun assertBlockedUserVisible(user: String) {
onView(usersTab).check(matches(isSelected()))
onView(withText(user)).check(matches(isDisplayed()))
onView(usersTab).waitVisible().check(matches(isSelected()))
onView(withText(user)).waitVisible()
}

fun assertBlockedUserNotVisible(user: String) {
onView(usersTab).check(matches(isSelected()))
onView(withText(user)).check(doesNotExist())
onView(usersTab).waitVisible().check(matches(isSelected()))
onView(withText(user)).waitNotVisible()
}

fun assertBlockedTagVisible(tag: String) {
onView(tagsTab).check(matches(isSelected()))
onView(withText(tag)).check(matches(isDisplayed()))
onView(tagsTab).waitVisible().check(matches(isSelected()))
onView(withText(tag)).waitVisible()
}

fun assertBlockedTagNotVisible(tag: String) {
onView(tagsTab).check(matches(isSelected()))
onView(withText(tag)).check(doesNotExist())
onView(tagsTab).waitVisible().check(matches(isSelected()))
onView(withText(tag)).waitNotVisible()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.test.espresso.contrib.NavigationViewActions.navigateTo
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import com.google.android.material.navigation.NavigationView
import io.github.wykopmobilny.tests.base.Page
import io.github.wykopmobilny.utils.waitVisible

object MainPage : Page {

Expand All @@ -17,14 +18,14 @@ object MainPage : Page {
private val navigationView = isAssignableFrom(NavigationView::class.java)

fun tapDrawerOption(@IdRes option: Int) {
onView(navigationView).perform(navigateTo(option))
onView(navigationView).waitVisible().perform(navigateTo(option))
}

fun openDrawer() {
onView(drawer).perform(open())
onView(drawer).waitVisible().perform(open())
}

fun closeDrawer() {
onView(drawer).perform(close())
onView(drawer).waitVisible().perform(close())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import io.github.wykopmobilny.R
import io.github.wykopmobilny.tests.matchers.onPreference
import io.github.wykopmobilny.tests.matchers.tapPreference
import io.github.wykopmobilny.utils.waitVisible

object SettingsPage {

Expand All @@ -30,18 +31,19 @@ object SettingsPage {

fun tapBlacklistSettings() {
onView(withId(R.id.recycler_view))
.waitVisible()
.perform(actionOnItem<ViewHolder>(hasDescendant(manageBlacklistOption), click()))
}

fun assertConfirmationOptionChecked() {
onPreference(confirmationOption).check(matches(isChecked()))
onPreference(confirmationOption).waitVisible().check(matches(isChecked()))
}

fun assertConfirmationOptionNotChecked() {
onPreference(confirmationOption).check(matches(isNotChecked()))
onPreference(confirmationOption).waitVisible().check(matches(isNotChecked()))
}

fun assertVisible() {
onView(withText("Ustawienia")).check(matches(isDisplayed()))
onView(withText("Ustawienia")).waitVisible().check(matches(isDisplayed()))
}
}
55 changes: 55 additions & 0 deletions app/src/androidTest/java/io/github/wykopmobilny/utils/Utils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.github.wykopmobilny.utils

import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.ViewAssertion
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.util.HumanReadables
import junit.framework.AssertionFailedError
import java.util.concurrent.TimeoutException

internal fun ViewInteraction.waitVisible(timeout: Long = 2000L): ViewInteraction {
val startTime = System.currentTimeMillis()
val endTime = startTime + timeout

do {
try {
check(matches(isDisplayed()))
return this
} catch (e: AssertionFailedError) {
Thread.sleep(50)
} catch (e: NoMatchingViewException) {
Thread.sleep(50)
}
} while (System.currentTimeMillis() < endTime)

throw TimeoutException()
}

internal fun ViewInteraction.waitNotVisible(timeout: Long = 2000L): ViewInteraction {
val startTime = System.currentTimeMillis()
val endTime = startTime + timeout

do {
try {
check(isNotDisplayed())
return this
} catch (e: AssertionFailedError) {
Thread.sleep(50)
} catch (e: NoMatchingViewException) {
Thread.sleep(50)
}
} while (System.currentTimeMillis() < endTime)

throw TimeoutException()
}

fun isNotDisplayed() = ViewAssertion { view, _ ->
if (isDisplayed().matches(view)) {
throw AssertionError(
"View is present in the hierarchy and Displayed: " +
HumanReadables.describe(view),
)
}
}
3 changes: 3 additions & 0 deletions app/src/androidTest/resources/2fa_success.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"data": []
}
10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@
android:parentActivityName="io.github.wykopmobilny.ui.modules.mainnavigation.MainNavigationActivity"
android:theme="@style/Theme.App.Dark"
/>
<activity
android:name="io.github.wykopmobilny.ui.modules.twofactor.TwoFactorAuthorizationActivity"
android:theme="@style/Theme.App.Dark"
android:windowSoftInputMode="adjustResize"
/>
<activity
android:name=".ui.modules.links.upvoters.UpvotersActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
Expand Down Expand Up @@ -372,11 +377,16 @@
android:screenOrientation="sensor"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
/>

<meta-data android:name="firebase_analytics_collection_deactivated" android:value="@bool/disable_firebase_analytics" />
</application>

<queries>
<intent>
<action android:name="android.intent.action.GET_CONTENT" />
<data android:mimeType="image/*" />
</intent>

<package android:name="com.google.android.apps.authenticator2" />
</queries>
</manifest>
42 changes: 39 additions & 3 deletions app/src/main/kotlin/io/github/wykopmobilny/WykopApp.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package io.github.wykopmobilny

import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.webkit.CookieManager
import android.widget.Toast
import androidx.core.app.ShareCompat
import androidx.core.net.toUri
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.jakewharton.threetenabp.AndroidThreeTen
Expand All @@ -30,6 +34,7 @@ import io.github.wykopmobilny.domain.search.di.SearchScope
import io.github.wykopmobilny.domain.settings.di.SettingsScope
import io.github.wykopmobilny.domain.startup.AppConfig
import io.github.wykopmobilny.domain.styles.di.StylesScope
import io.github.wykopmobilny.domain.twofactor.di.TwoFactorAuthScope
import io.github.wykopmobilny.domain.work.di.WorkScope
import io.github.wykopmobilny.initializers.RemoteConfigKeys
import io.github.wykopmobilny.links.details.LinkDetailsDependencies
Expand Down Expand Up @@ -60,6 +65,7 @@ import io.github.wykopmobilny.ui.modules.tag.TagActivity
import io.github.wykopmobilny.ui.profile.ProfileDependencies
import io.github.wykopmobilny.ui.search.SearchDependencies
import io.github.wykopmobilny.ui.settings.SettingsDependencies
import io.github.wykopmobilny.ui.twofactor.TwoFactorAuthDependencies
import io.github.wykopmobilny.utils.ApplicationInjector
import io.github.wykopmobilny.utils.linkhandler.WykopLinkHandler
import io.github.wykopmobilny.utils.requireDependency
Expand All @@ -73,7 +79,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand All @@ -85,6 +90,7 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.reflect.KClass
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

open class WykopApp : DaggerApplication(), ApplicationInjector, AppScopes {

Expand Down Expand Up @@ -161,9 +167,9 @@ open class WykopApp : DaggerApplication(), ApplicationInjector, AppScopes {
private val firebase
get() = FirebaseRemoteConfig.getInstance()
override val blacklistRefreshInterval: Duration
get() = Duration.milliseconds(firebase.getLong(RemoteConfigKeys.BLACKLIST_REFRESH_INTERVAL))
get() = firebase.getLong(RemoteConfigKeys.BLACKLIST_REFRESH_INTERVAL).milliseconds
override val blacklistFlexInterval: Duration
get() = Duration.milliseconds(firebase.getLong(RemoteConfigKeys.BLACKLIST_FLEX_INTERVAL))
get() = firebase.getLong(RemoteConfigKeys.BLACKLIST_FLEX_INTERVAL).milliseconds
override val notificationsEnabled: Boolean
get() = firebase.getBoolean(RemoteConfigKeys.NOTIFICATIONS_ENABLED)
}
Expand Down Expand Up @@ -266,6 +272,7 @@ open class WykopApp : DaggerApplication(), ApplicationInjector, AppScopes {
WorkDependencies::class -> getOrPutScope<WorkScope>(scopeId) { domainComponent.work() }
SearchDependencies::class -> getOrPutScope<SearchScope>(scopeId) { domainComponent.search() }
NotificationDependencies::class -> getOrPutScope<NotificationsScope>(scopeId) { domainComponent.notifications() }
TwoFactorAuthDependencies::class -> getOrPutScope<TwoFactorAuthScope>(scopeId) { domainComponent.twoFactor() }
LinkDetailsDependencies::class -> {
scopeId as LinkDetailsKey
getOrPutScope<LinkDetailsScope>(scopeId) { domainComponent.linkDetails().create(key = scopeId) }
Expand Down Expand Up @@ -306,6 +313,7 @@ open class WykopApp : DaggerApplication(), ApplicationInjector, AppScopes {
LinkDetailsDependencies::class -> scopes.remove(scopeKey<LinkDetailsScope>(scopeId))
ProfileDependencies::class -> scopes.remove(scopeKey<ProfileScope>(scopeId))
NotificationDependencies::class -> scopes.remove(scopeKey<NotificationsScope>(scopeId))
TwoFactorAuthDependencies::class -> scopes.remove(scopeKey<TwoFactorAuthScope>(scopeId))
else -> error("Unknown dependency type $clazz")
}?.coroutineScope?.cancel()
}
Expand Down Expand Up @@ -403,9 +411,37 @@ open class WykopApp : DaggerApplication(), ApplicationInjector, AppScopes {
is InteropRequest.OpenYoutube -> context.startActivity(YoutubeActivity.createIntent(context, it.url))
is InteropRequest.ShowGif -> context.startActivity(PhotoViewActivity.createIntent(context, it.url))
is InteropRequest.ShowImage -> context.startActivity(PhotoViewActivity.createIntent(context, it.url))
InteropRequest.OpenGoogleAuthenticator -> context.openApp("com.google.android.apps.authenticator2")
}
.run { }
}
}
}

private fun Activity.openApp(appId: String) {
if (isAppInstalled(appId)) {
startActivity(packageManager.getLaunchIntentForPackage(appId))
} else {
openStoreListing(appId)
}
}

private fun Activity.openStoreListing(appId: String) {
try {
startActivity(Intent(Intent.ACTION_VIEW, "market://details?id=$appId".toUri()))
} catch (ignored: ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, "https://play.google.com/store/apps/details?id=$appId".toUri()))
}
}

@Suppress("TooGenericExceptionCaught")
private fun Context.isAppInstalled(appId: String): Boolean = try {
packageManager.getPackageInfo(appId, PackageManager.GET_ACTIVITIES)
true
} catch (ignored: PackageManager.NameNotFoundException) {
false
} catch (throwable: Throwable) {
Napier.w(message = "Unexpected error when checking if app is installed", throwable)
false
}
}

0 comments on commit 1a3de98

Please sign in to comment.