Skip to content

Commit

Permalink
implemented onboarding
Browse files Browse the repository at this point in the history
  • Loading branch information
surinder-tsys committed Aug 5, 2024
1 parent cf222d4 commit 10e3a03
Show file tree
Hide file tree
Showing 28 changed files with 718 additions and 171 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.nmc.android.onboarding

import androidx.test.espresso.Espresso.*
import androidx.test.espresso.action.ViewActions.swipeLeft
import androidx.test.espresso.action.ViewActions.swipeRight
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isClickable
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.client.onboarding.FirstRunActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class OnBoardingIT : AbstractIT() {

@get:Rule
var activityRule = ActivityScenarioRule(FirstRunActivity::class.java)

@Test
fun runAllOnboardingTests() {
verifyUIElements()

shortSleep()

verifyOnBoardingSwipe()
}

private fun verifyUIElements() {
onView(withId(R.id.contentPanel)).check(matches(isCompletelyDisplayed()))
onView(withId(R.id.progressIndicator)).check(matches(isCompletelyDisplayed()))
onView(withId(R.id.login)).check(matches(isCompletelyDisplayed()))
onView(withId(R.id.login)).check(matches(isClickable()))
}

private fun verifyOnBoardingSwipe() {
onView(withId(R.id.contentPanel)).perform(swipeLeft())
onView(withId(R.id.contentPanel)).perform(swipeLeft())
onView(withId(R.id.contentPanel)).perform(swipeLeft())

onView(withId(R.id.contentPanel)).perform(swipeRight())
onView(withId(R.id.contentPanel)).perform(swipeRight())
}
}
164 changes: 75 additions & 89 deletions app/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,39 @@
package com.nextcloud.client.onboarding

import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager.widget.ViewPager
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.appinfo.AppInfo
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.preferences.AppPreferences
import com.nmc.android.helper.OnBoardingPagerAdapter
import com.nmc.android.helper.OnBoardingUtils.Companion.getOnBoardingItems
import com.nmc.android.utils.DisplayUtils.isLandscapeOrientation
import com.owncloud.android.BuildConfig
import com.owncloud.android.R
import com.owncloud.android.authentication.AuthenticatorActivity
import com.owncloud.android.databinding.FirstRunActivityBinding
import com.owncloud.android.features.FeatureItem
import com.owncloud.android.ui.activity.BaseActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.adapter.FeaturesViewAdapter
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import javax.inject.Inject

/**
* Activity displaying general feature after a fresh install.
*/
class FirstRunActivity : BaseActivity(), Injectable {
class FirstRunActivity : BaseActivity(), ViewPager.OnPageChangeListener, Injectable {

@JvmField
@Inject
Expand All @@ -66,25 +67,31 @@ class FirstRunActivity : BaseActivity(), Injectable {
private lateinit var binding: FirstRunActivityBinding
private var defaultViewThemeUtils: ViewThemeUtils? = null

private var selectedPosition = 0

@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
enableAccountHandling = false

super.onCreate(savedInstanceState)

applyDefaultTheme()

// NMC Customization
// if device is not tablet then we have to lock it to Portrait mode
// as we don't have images for that
if (!com.nmc.android.utils.DisplayUtils.isTablet()) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}

binding = FirstRunActivityBinding.inflate(layoutInflater)
setContentView(binding.root)

val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
setSlideshowSize(resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)

registerActivityResult()
setupLoginButton()
setupSignupButton(isProviderOrOwnInstallationVisible)
setupHostOwnServerTextView(isProviderOrOwnInstallationVisible)
deleteAccountAtFirstLaunch()
setupFeaturesViewAdapter()
updateLoginButtonMargin()
updateOnBoardingPager(selectedPosition)
handleOnBackPressed()
}

Expand Down Expand Up @@ -123,62 +130,53 @@ class FirstRunActivity : BaseActivity(), Injectable {
val authenticatorActivityIntent = getAuthenticatorActivityIntent(false)
activityResult?.launch(authenticatorActivityIntent)
} else {
preferences?.onBoardingComplete = true
finish()
}
}
}

private fun setupSignupButton(isProviderOrOwnInstallationVisible: Boolean) {
defaultViewThemeUtils?.material?.colorMaterialButtonOutlinedOnPrimary(binding.signup)
binding.signup.visibility = if (isProviderOrOwnInstallationVisible) View.VISIBLE else View.GONE
binding.signup.setOnClickListener {
val authenticatorActivityIntent = getAuthenticatorActivityIntent(true)

if (intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
activityResult?.launch(authenticatorActivityIntent)
} else {
authenticatorActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(authenticatorActivityIntent)
}
}
}

private fun getAuthenticatorActivityIntent(extraUseProviderAsWebLogin: Boolean): Intent {
val intent = Intent(this, AuthenticatorActivity::class.java)
intent.putExtra(AuthenticatorActivity.EXTRA_USE_PROVIDER_AS_WEBLOGIN, extraUseProviderAsWebLogin)
return intent
}

private fun setupHostOwnServerTextView(isProviderOrOwnInstallationVisible: Boolean) {
defaultViewThemeUtils?.platform?.colorTextView(binding.hostOwnServer, ColorRole.ON_PRIMARY)
binding.hostOwnServer.visibility = if (isProviderOrOwnInstallationVisible) View.VISIBLE else View.GONE
if (isProviderOrOwnInstallationVisible) {
binding.hostOwnServer.setOnClickListener {
DisplayUtils.startLinkIntent(
this,
R.string.url_server_install
)
}
}
}

// Sometimes, accounts are not deleted when you uninstall the application so we'll do it now
private fun deleteAccountAtFirstLaunch() {
if (onboarding?.isFirstRun == true) {
userAccountManager?.removeAllAccounts()
}
}

@Suppress("SpreadOperator")
private fun setupFeaturesViewAdapter() {
val featuresViewAdapter = FeaturesViewAdapter(this, *firstRun)
binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.itemCount)
binding.contentPanel.adapter = featuresViewAdapter
binding.contentPanel.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
binding.progressIndicator.animateToStep(position + 1)
private fun updateLoginButtonMargin() {
if (isLandscapeOrientation()) {
if (binding.login.layoutParams is MarginLayoutParams) {
(binding.login.layoutParams as MarginLayoutParams).setMargins(
0, 0, 0, resources.getDimensionPixelOffset(
R.dimen.login_btn_bottom_margin_land
)
)
binding.login.requestLayout()
}
} else {
if (binding.login.layoutParams is MarginLayoutParams) {
(binding.login.layoutParams as MarginLayoutParams).setMargins(
0, 0, 0, resources.getDimensionPixelOffset(
R.dimen.login_btn_bottom_margin
)
)
binding.login.requestLayout()
}
})
}
}

private fun updateOnBoardingPager(selectedPosition: Int) {
val featuresViewAdapter = OnBoardingPagerAdapter(this, getOnBoardingItems())
binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.count)
binding.contentPanel.adapter = featuresViewAdapter
binding.contentPanel.currentItem = selectedPosition
binding.contentPanel.addOnPageChangeListener(this)
}

private fun handleOnBackPressed() {
Expand All @@ -188,47 +186,27 @@ class FirstRunActivity : BaseActivity(), Injectable {
override fun handleOnBackPressed() {
val isFromAddAccount = intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)

val destination: Intent = if (isFromAddAccount) {
Intent(applicationContext, FileDisplayActivity::class.java)
// NMC Customization -> Modified the condition for readability
if (isFromAddAccount) {
val destination = Intent(applicationContext, FileDisplayActivity::class.java)
destination.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(destination)
finish()
} else {
Intent(applicationContext, AuthenticatorActivity::class.java)
}

if (!isFromAddAccount) {
destination.putExtra(EXTRA_EXIT, true)
// NMC Customization -> No redirection to AuthenticatorActivity is required
// just close the app
finishAffinity()
}

destination.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(destination)
finish()
}
}
)
}

private fun setSlideshowSize(isLandscape: Boolean) {
val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
binding.buttonLayout.orientation = if (isLandscape) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL

val layoutParams: LinearLayout.LayoutParams = if (isProviderOrOwnInstallationVisible) {
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
} else {
@Suppress("MagicNumber")
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
DisplayUtils.convertDpToPixel(if (isLandscape) 100f else 150f, this)
)
}

binding.bottomLayout.layoutParams = layoutParams
}

override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
setSlideshowSize(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
updateLoginButtonMargin()
updateOnBoardingPager(selectedPosition)
}

private fun onFinish() {
Expand All @@ -240,16 +218,24 @@ class FirstRunActivity : BaseActivity(), Injectable {
super.onStop()
}

override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
// unused but to be implemented due to abstract parent
}

override fun onPageSelected(position: Int) {
//-1 to position because this position doesn't start from 0
selectedPosition = position - 1

//pass directly the position here because this position will doesn't start from 0
binding.progressIndicator.animateToStep(position)
}

override fun onPageScrollStateChanged(state: Int) {
// unused but to be implemented due to abstract parent
}

companion object {
const val EXTRA_ALLOW_CLOSE = "ALLOW_CLOSE"
const val EXTRA_EXIT = "EXIT"

val firstRun: Array<FeatureItem>
get() = arrayOf(
FeatureItem(R.drawable.logo, R.string.first_run_1_text, R.string.empty, true, false),
FeatureItem(R.drawable.first_run_files, R.string.first_run_2_text, R.string.empty, true, false),
FeatureItem(R.drawable.first_run_groupware, R.string.first_run_3_text, R.string.empty, true, false),
FeatureItem(R.drawable.first_run_talk, R.string.first_run_4_text, R.string.empty, true, false)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ internal class OnboardingServiceImpl constructor(
}

override fun launchFirstRunIfNeeded(activity: Activity): Boolean {
val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
val canLaunch = isProviderOrOwnInstallationVisible && isFirstRun && activity is AuthenticatorActivity
val canLaunch = !preferences.onBoardingComplete && activity is AuthenticatorActivity
if (canLaunch) {
val intent = Intent(activity, FirstRunActivity::class.java)
activity.startActivityForResult(intent, AuthenticatorActivity.REQUEST_CODE_FIRST_RUN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ default void onDarkThemeModeChanged(DarkMode mode) {

void setGlobalUploadPaused(boolean globalPausedState);

void setOnBoardingComplete(boolean isCompleted);

boolean getOnBoardingComplete();

void setPdfZoomTipShownCount(int count);

int getPdfZoomTipShownCount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ public final class AppPreferencesImpl implements AppPreferences {
private static final String PREF__FOLDER_SORT_ORDER = "folder_sort_order";
private static final String PREF__FOLDER_LAYOUT = "folder_layout";

private static final String PREF__ON_BOARDING_COMPLETE = "on_boarding_complete";

private static final String PREF__LOCK_TIMESTAMP = "lock_timestamp";
private static final String PREF__SHOW_MEDIA_SCAN_NOTIFICATIONS = "show_media_scan_notifications";
private static final String PREF__LOCK = SettingsActivity.PREFERENCE_LOCK;
Expand Down Expand Up @@ -740,6 +742,16 @@ public void setGlobalUploadPaused(boolean globalPausedState) {
preferences.edit().putBoolean(PREF__GLOBAL_PAUSE_STATE, globalPausedState).apply();
}

@Override
public void setOnBoardingComplete(boolean isCompleted) {
preferences.edit().putBoolean(PREF__ON_BOARDING_COMPLETE, isCompleted).apply();
}

@Override
public boolean getOnBoardingComplete() {
return preferences.getBoolean(PREF__ON_BOARDING_COMPLETE, false);
}

@Override
public void setPdfZoomTipShownCount(int count) {
preferences.edit().putInt(PREF__PDF_ZOOM_TIP_SHOWN, count).apply();
Expand Down
Loading

0 comments on commit 10e3a03

Please sign in to comment.