Skip to content

Commit dfa1207

Browse files
committed
implemented onboarding
1 parent e0cc413 commit dfa1207

28 files changed

+718
-171
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.nmc.android.onboarding
2+
3+
import androidx.test.espresso.Espresso.*
4+
import androidx.test.espresso.action.ViewActions.swipeLeft
5+
import androidx.test.espresso.action.ViewActions.swipeRight
6+
import androidx.test.espresso.assertion.ViewAssertions.matches
7+
import androidx.test.espresso.matcher.ViewMatchers.isClickable
8+
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
9+
import androidx.test.espresso.matcher.ViewMatchers.withId
10+
import androidx.test.ext.junit.rules.ActivityScenarioRule
11+
import androidx.test.ext.junit.runners.AndroidJUnit4
12+
import com.nextcloud.client.onboarding.FirstRunActivity
13+
import com.owncloud.android.AbstractIT
14+
import com.owncloud.android.R
15+
import org.junit.Rule
16+
import org.junit.Test
17+
import org.junit.runner.RunWith
18+
19+
@RunWith(AndroidJUnit4::class)
20+
class OnBoardingIT : AbstractIT() {
21+
22+
@get:Rule
23+
var activityRule = ActivityScenarioRule(FirstRunActivity::class.java)
24+
25+
@Test
26+
fun runAllOnboardingTests() {
27+
verifyUIElements()
28+
29+
shortSleep()
30+
31+
verifyOnBoardingSwipe()
32+
}
33+
34+
private fun verifyUIElements() {
35+
onView(withId(R.id.contentPanel)).check(matches(isCompletelyDisplayed()))
36+
onView(withId(R.id.progressIndicator)).check(matches(isCompletelyDisplayed()))
37+
onView(withId(R.id.login)).check(matches(isCompletelyDisplayed()))
38+
onView(withId(R.id.login)).check(matches(isClickable()))
39+
}
40+
41+
private fun verifyOnBoardingSwipe() {
42+
onView(withId(R.id.contentPanel)).perform(swipeLeft())
43+
onView(withId(R.id.contentPanel)).perform(swipeLeft())
44+
onView(withId(R.id.contentPanel)).perform(swipeLeft())
45+
46+
onView(withId(R.id.contentPanel)).perform(swipeRight())
47+
onView(withId(R.id.contentPanel)).perform(swipeRight())
48+
}
49+
}

app/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.kt

Lines changed: 75 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,39 @@
88
package com.nextcloud.client.onboarding
99

1010
import android.accounts.AccountManager
11+
import android.annotation.SuppressLint
1112
import android.content.Intent
13+
import android.content.pm.ActivityInfo
1214
import android.content.res.Configuration
1315
import android.os.Bundle
14-
import android.view.View
15-
import android.view.ViewGroup
16-
import android.widget.LinearLayout
16+
import android.view.ViewGroup.MarginLayoutParams
1717
import androidx.activity.OnBackPressedCallback
1818
import androidx.activity.result.ActivityResult
1919
import androidx.activity.result.ActivityResultLauncher
2020
import androidx.activity.result.contract.ActivityResultContracts
21-
import androidx.viewpager2.widget.ViewPager2
21+
import androidx.viewpager.widget.ViewPager
2222
import com.nextcloud.android.common.ui.theme.utils.ColorRole
2323
import com.nextcloud.client.account.UserAccountManager
2424
import com.nextcloud.client.appinfo.AppInfo
2525
import com.nextcloud.client.di.Injectable
2626
import com.nextcloud.client.preferences.AppPreferences
27+
import com.nmc.android.helper.OnBoardingPagerAdapter
28+
import com.nmc.android.helper.OnBoardingUtils.Companion.getOnBoardingItems
29+
import com.nmc.android.utils.DisplayUtils.isLandscapeOrientation
2730
import com.owncloud.android.BuildConfig
2831
import com.owncloud.android.R
2932
import com.owncloud.android.authentication.AuthenticatorActivity
3033
import com.owncloud.android.databinding.FirstRunActivityBinding
31-
import com.owncloud.android.features.FeatureItem
3234
import com.owncloud.android.ui.activity.BaseActivity
3335
import com.owncloud.android.ui.activity.FileDisplayActivity
34-
import com.owncloud.android.ui.adapter.FeaturesViewAdapter
3536
import com.owncloud.android.utils.DisplayUtils
3637
import com.owncloud.android.utils.theme.ViewThemeUtils
3738
import javax.inject.Inject
3839

3940
/**
4041
* Activity displaying general feature after a fresh install.
4142
*/
42-
class FirstRunActivity : BaseActivity(), Injectable {
43+
class FirstRunActivity : BaseActivity(), ViewPager.OnPageChangeListener, Injectable {
4344

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

70+
private var selectedPosition = 0
71+
72+
@SuppressLint("SourceLockedOrientationActivity")
6973
override fun onCreate(savedInstanceState: Bundle?) {
7074
enableAccountHandling = false
7175

7276
super.onCreate(savedInstanceState)
7377

7478
applyDefaultTheme()
7579

80+
// NMC Customization
81+
// if device is not tablet then we have to lock it to Portrait mode
82+
// as we don't have images for that
83+
if (!com.nmc.android.utils.DisplayUtils.isTablet()) {
84+
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
85+
}
86+
7687
binding = FirstRunActivityBinding.inflate(layoutInflater)
7788
setContentView(binding.root)
7889

79-
val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
80-
setSlideshowSize(resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
81-
8290
registerActivityResult()
8391
setupLoginButton()
84-
setupSignupButton(isProviderOrOwnInstallationVisible)
85-
setupHostOwnServerTextView(isProviderOrOwnInstallationVisible)
8692
deleteAccountAtFirstLaunch()
87-
setupFeaturesViewAdapter()
93+
updateLoginButtonMargin()
94+
updateOnBoardingPager(selectedPosition)
8895
handleOnBackPressed()
8996
}
9097

@@ -123,62 +130,53 @@ class FirstRunActivity : BaseActivity(), Injectable {
123130
val authenticatorActivityIntent = getAuthenticatorActivityIntent(false)
124131
activityResult?.launch(authenticatorActivityIntent)
125132
} else {
133+
preferences?.onBoardingComplete = true
126134
finish()
127135
}
128136
}
129137
}
130138

131-
private fun setupSignupButton(isProviderOrOwnInstallationVisible: Boolean) {
132-
defaultViewThemeUtils?.material?.colorMaterialButtonOutlinedOnPrimary(binding.signup)
133-
binding.signup.visibility = if (isProviderOrOwnInstallationVisible) View.VISIBLE else View.GONE
134-
binding.signup.setOnClickListener {
135-
val authenticatorActivityIntent = getAuthenticatorActivityIntent(true)
136-
137-
if (intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
138-
activityResult?.launch(authenticatorActivityIntent)
139-
} else {
140-
authenticatorActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
141-
startActivity(authenticatorActivityIntent)
142-
}
143-
}
144-
}
145-
146139
private fun getAuthenticatorActivityIntent(extraUseProviderAsWebLogin: Boolean): Intent {
147140
val intent = Intent(this, AuthenticatorActivity::class.java)
148141
intent.putExtra(AuthenticatorActivity.EXTRA_USE_PROVIDER_AS_WEBLOGIN, extraUseProviderAsWebLogin)
149142
return intent
150143
}
151144

152-
private fun setupHostOwnServerTextView(isProviderOrOwnInstallationVisible: Boolean) {
153-
defaultViewThemeUtils?.platform?.colorTextView(binding.hostOwnServer, ColorRole.ON_PRIMARY)
154-
binding.hostOwnServer.visibility = if (isProviderOrOwnInstallationVisible) View.VISIBLE else View.GONE
155-
if (isProviderOrOwnInstallationVisible) {
156-
binding.hostOwnServer.setOnClickListener {
157-
DisplayUtils.startLinkIntent(
158-
this,
159-
R.string.url_server_install
160-
)
161-
}
162-
}
163-
}
164-
165145
// Sometimes, accounts are not deleted when you uninstall the application so we'll do it now
166146
private fun deleteAccountAtFirstLaunch() {
167147
if (onboarding?.isFirstRun == true) {
168148
userAccountManager?.removeAllAccounts()
169149
}
170150
}
171151

172-
@Suppress("SpreadOperator")
173-
private fun setupFeaturesViewAdapter() {
174-
val featuresViewAdapter = FeaturesViewAdapter(this, *firstRun)
175-
binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.itemCount)
176-
binding.contentPanel.adapter = featuresViewAdapter
177-
binding.contentPanel.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
178-
override fun onPageSelected(position: Int) {
179-
binding.progressIndicator.animateToStep(position + 1)
152+
private fun updateLoginButtonMargin() {
153+
if (isLandscapeOrientation()) {
154+
if (binding.login.layoutParams is MarginLayoutParams) {
155+
(binding.login.layoutParams as MarginLayoutParams).setMargins(
156+
0, 0, 0, resources.getDimensionPixelOffset(
157+
R.dimen.login_btn_bottom_margin_land
158+
)
159+
)
160+
binding.login.requestLayout()
161+
}
162+
} else {
163+
if (binding.login.layoutParams is MarginLayoutParams) {
164+
(binding.login.layoutParams as MarginLayoutParams).setMargins(
165+
0, 0, 0, resources.getDimensionPixelOffset(
166+
R.dimen.login_btn_bottom_margin
167+
)
168+
)
169+
binding.login.requestLayout()
180170
}
181-
})
171+
}
172+
}
173+
174+
private fun updateOnBoardingPager(selectedPosition: Int) {
175+
val featuresViewAdapter = OnBoardingPagerAdapter(this, getOnBoardingItems())
176+
binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.count)
177+
binding.contentPanel.adapter = featuresViewAdapter
178+
binding.contentPanel.currentItem = selectedPosition
179+
binding.contentPanel.addOnPageChangeListener(this)
182180
}
183181

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

191-
val destination: Intent = if (isFromAddAccount) {
192-
Intent(applicationContext, FileDisplayActivity::class.java)
189+
// NMC Customization -> Modified the condition for readability
190+
if (isFromAddAccount) {
191+
val destination = Intent(applicationContext, FileDisplayActivity::class.java)
192+
destination.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
193+
startActivity(destination)
194+
finish()
193195
} else {
194-
Intent(applicationContext, AuthenticatorActivity::class.java)
195-
}
196-
197-
if (!isFromAddAccount) {
198-
destination.putExtra(EXTRA_EXIT, true)
196+
// NMC Customization -> No redirection to AuthenticatorActivity is required
197+
// just close the app
198+
finishAffinity()
199199
}
200200

201-
destination.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
202-
startActivity(destination)
203-
finish()
204201
}
205202
}
206203
)
207204
}
208205

209-
private fun setSlideshowSize(isLandscape: Boolean) {
210-
val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
211-
binding.buttonLayout.orientation = if (isLandscape) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
212-
213-
val layoutParams: LinearLayout.LayoutParams = if (isProviderOrOwnInstallationVisible) {
214-
LinearLayout.LayoutParams(
215-
ViewGroup.LayoutParams.MATCH_PARENT,
216-
ViewGroup.LayoutParams.WRAP_CONTENT
217-
)
218-
} else {
219-
@Suppress("MagicNumber")
220-
LinearLayout.LayoutParams(
221-
ViewGroup.LayoutParams.MATCH_PARENT,
222-
DisplayUtils.convertDpToPixel(if (isLandscape) 100f else 150f, this)
223-
)
224-
}
225-
226-
binding.bottomLayout.layoutParams = layoutParams
227-
}
228-
229206
override fun onConfigurationChanged(newConfig: Configuration) {
230207
super.onConfigurationChanged(newConfig)
231-
setSlideshowSize(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
208+
updateLoginButtonMargin()
209+
updateOnBoardingPager(selectedPosition)
232210
}
233211

234212
private fun onFinish() {
@@ -240,16 +218,24 @@ class FirstRunActivity : BaseActivity(), Injectable {
240218
super.onStop()
241219
}
242220

221+
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
222+
// unused but to be implemented due to abstract parent
223+
}
224+
225+
override fun onPageSelected(position: Int) {
226+
//-1 to position because this position doesn't start from 0
227+
selectedPosition = position - 1
228+
229+
//pass directly the position here because this position will doesn't start from 0
230+
binding.progressIndicator.animateToStep(position)
231+
}
232+
233+
override fun onPageScrollStateChanged(state: Int) {
234+
// unused but to be implemented due to abstract parent
235+
}
236+
243237
companion object {
244238
const val EXTRA_ALLOW_CLOSE = "ALLOW_CLOSE"
245239
const val EXTRA_EXIT = "EXIT"
246-
247-
val firstRun: Array<FeatureItem>
248-
get() = arrayOf(
249-
FeatureItem(R.drawable.logo, R.string.first_run_1_text, R.string.empty, true, false),
250-
FeatureItem(R.drawable.first_run_files, R.string.first_run_2_text, R.string.empty, true, false),
251-
FeatureItem(R.drawable.first_run_groupware, R.string.first_run_3_text, R.string.empty, true, false),
252-
FeatureItem(R.drawable.first_run_talk, R.string.first_run_4_text, R.string.empty, true, false)
253-
)
254240
}
255241
}

app/src/main/java/com/nextcloud/client/onboarding/OnboardingServiceImpl.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ internal class OnboardingServiceImpl constructor(
6060
}
6161

6262
override fun launchFirstRunIfNeeded(activity: Activity): Boolean {
63-
val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
64-
val canLaunch = isProviderOrOwnInstallationVisible && isFirstRun && activity is AuthenticatorActivity
63+
val canLaunch = !preferences.onBoardingComplete && activity is AuthenticatorActivity
6564
if (canLaunch) {
6665
val intent = Intent(activity, FirstRunActivity::class.java)
6766
activity.startActivityForResult(intent, AuthenticatorActivity.REQUEST_CODE_FIRST_RUN)

app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,10 @@ default void onDarkThemeModeChanged(DarkMode mode) {
374374

375375
void setGlobalUploadPaused(boolean globalPausedState);
376376

377+
void setOnBoardingComplete(boolean isCompleted);
378+
379+
boolean getOnBoardingComplete();
380+
377381
void setPdfZoomTipShownCount(int count);
378382

379383
int getPdfZoomTipShownCount();

app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public final class AppPreferencesImpl implements AppPreferences {
8181
private static final String PREF__FOLDER_SORT_ORDER = "folder_sort_order";
8282
private static final String PREF__FOLDER_LAYOUT = "folder_layout";
8383

84+
private static final String PREF__ON_BOARDING_COMPLETE = "on_boarding_complete";
85+
8486
private static final String PREF__LOCK_TIMESTAMP = "lock_timestamp";
8587
private static final String PREF__SHOW_MEDIA_SCAN_NOTIFICATIONS = "show_media_scan_notifications";
8688
private static final String PREF__LOCK = SettingsActivity.PREFERENCE_LOCK;
@@ -740,6 +742,16 @@ public void setGlobalUploadPaused(boolean globalPausedState) {
740742
preferences.edit().putBoolean(PREF__GLOBAL_PAUSE_STATE, globalPausedState).apply();
741743
}
742744

745+
@Override
746+
public void setOnBoardingComplete(boolean isCompleted) {
747+
preferences.edit().putBoolean(PREF__ON_BOARDING_COMPLETE, isCompleted).apply();
748+
}
749+
750+
@Override
751+
public boolean getOnBoardingComplete() {
752+
return preferences.getBoolean(PREF__ON_BOARDING_COMPLETE, false);
753+
}
754+
743755
@Override
744756
public void setPdfZoomTipShownCount(int count) {
745757
preferences.edit().putInt(PREF__PDF_ZOOM_TIP_SHOWN, count).apply();

0 commit comments

Comments
 (0)