Skip to content

Commit 2fc576d

Browse files
committed
implemented onboarding
1 parent d4b4689 commit 2fc576d

28 files changed

Lines changed: 718 additions & 168 deletions
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 & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,40 @@
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
2727
import com.nextcloud.utils.mdm.MDMConfig
28+
import com.nmc.android.helper.OnBoardingPagerAdapter
29+
import com.nmc.android.helper.OnBoardingUtils.Companion.getOnBoardingItems
30+
import com.nmc.android.utils.DisplayUtils.isLandscapeOrientation
2831
import com.owncloud.android.BuildConfig
2932
import com.owncloud.android.R
3033
import com.owncloud.android.authentication.AuthenticatorActivity
3134
import com.owncloud.android.databinding.FirstRunActivityBinding
32-
import com.owncloud.android.features.FeatureItem
3335
import com.owncloud.android.ui.activity.BaseActivity
3436
import com.owncloud.android.ui.activity.FileDisplayActivity
35-
import com.owncloud.android.ui.adapter.FeaturesViewAdapter
3637
import com.owncloud.android.utils.DisplayUtils
3738
import com.owncloud.android.utils.theme.ViewThemeUtils
3839
import javax.inject.Inject
3940

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

4546
@JvmField
4647
@Inject
@@ -67,24 +68,31 @@ class FirstRunActivity : BaseActivity(), Injectable {
6768
private lateinit var binding: FirstRunActivityBinding
6869
private var defaultViewThemeUtils: ViewThemeUtils? = null
6970

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

7377
super.onCreate(savedInstanceState)
7478

7579
applyDefaultTheme()
7680

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

80-
setSlideshowSize(resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
81-
8291
registerActivityResult()
8392
setupLoginButton()
84-
setupSignupButton(MDMConfig.showIntro(this))
85-
setupHostOwnServerTextView(MDMConfig.showIntro(this))
8693
deleteAccountAtFirstLaunch()
87-
setupFeaturesViewAdapter()
94+
updateLoginButtonMargin()
95+
updateOnBoardingPager(selectedPosition)
8896
handleOnBackPressed()
8997
}
9098

@@ -123,62 +131,53 @@ class FirstRunActivity : BaseActivity(), Injectable {
123131
val authenticatorActivityIntent = getAuthenticatorActivityIntent(false)
124132
activityResult?.launch(authenticatorActivityIntent)
125133
} else {
134+
preferences?.onBoardingComplete = true
126135
finish()
127136
}
128137
}
129138
}
130139

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-
146140
private fun getAuthenticatorActivityIntent(extraUseProviderAsWebLogin: Boolean): Intent {
147141
val intent = Intent(this, AuthenticatorActivity::class.java)
148142
intent.putExtra(AuthenticatorActivity.EXTRA_USE_PROVIDER_AS_WEBLOGIN, extraUseProviderAsWebLogin)
149143
return intent
150144
}
151145

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-
165146
// Sometimes, accounts are not deleted when you uninstall the application so we'll do it now
166147
private fun deleteAccountAtFirstLaunch() {
167148
if (onboarding?.isFirstRun == true) {
168149
userAccountManager?.removeAllAccounts()
169150
}
170151
}
171152

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)
153+
private fun updateLoginButtonMargin() {
154+
if (isLandscapeOrientation()) {
155+
if (binding.login.layoutParams is MarginLayoutParams) {
156+
(binding.login.layoutParams as MarginLayoutParams).setMargins(
157+
0, 0, 0, resources.getDimensionPixelOffset(
158+
R.dimen.login_btn_bottom_margin_land
159+
)
160+
)
161+
binding.login.requestLayout()
162+
}
163+
} else {
164+
if (binding.login.layoutParams is MarginLayoutParams) {
165+
(binding.login.layoutParams as MarginLayoutParams).setMargins(
166+
0, 0, 0, resources.getDimensionPixelOffset(
167+
R.dimen.login_btn_bottom_margin
168+
)
169+
)
170+
binding.login.requestLayout()
180171
}
181-
})
172+
}
173+
}
174+
175+
private fun updateOnBoardingPager(selectedPosition: Int) {
176+
val featuresViewAdapter = OnBoardingPagerAdapter(this, getOnBoardingItems())
177+
binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.count)
178+
binding.contentPanel.adapter = featuresViewAdapter
179+
binding.contentPanel.currentItem = selectedPosition
180+
binding.contentPanel.addOnPageChangeListener(this)
182181
}
183182

184183
private fun handleOnBackPressed() {
@@ -188,46 +187,27 @@ class FirstRunActivity : BaseActivity(), Injectable {
188187
override fun handleOnBackPressed() {
189188
val isFromAddAccount = intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)
190189

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

201-
destination.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
202-
startActivity(destination)
203-
finish()
204202
}
205203
}
206204
)
207205
}
208206

209-
private fun setSlideshowSize(isLandscape: Boolean) {
210-
binding.buttonLayout.orientation = if (isLandscape) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
211-
212-
val layoutParams: LinearLayout.LayoutParams = if (MDMConfig.showIntro(this)) {
213-
LinearLayout.LayoutParams(
214-
ViewGroup.LayoutParams.MATCH_PARENT,
215-
ViewGroup.LayoutParams.WRAP_CONTENT
216-
)
217-
} else {
218-
@Suppress("MagicNumber")
219-
LinearLayout.LayoutParams(
220-
ViewGroup.LayoutParams.MATCH_PARENT,
221-
DisplayUtils.convertDpToPixel(if (isLandscape) 100f else 150f, this)
222-
)
223-
}
224-
225-
binding.bottomLayout.layoutParams = layoutParams
226-
}
227-
228207
override fun onConfigurationChanged(newConfig: Configuration) {
229208
super.onConfigurationChanged(newConfig)
230-
setSlideshowSize(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
209+
updateLoginButtonMargin()
210+
updateOnBoardingPager(selectedPosition)
231211
}
232212

233213
private fun onFinish() {
@@ -239,16 +219,24 @@ class FirstRunActivity : BaseActivity(), Injectable {
239219
super.onStop()
240220
}
241221

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ internal class OnboardingServiceImpl(
6262
}
6363

6464
override fun launchFirstRunIfNeeded(activity: Activity): Boolean {
65-
val canLaunch = MDMConfig.showIntro(activity) && isFirstRun && activity is AuthenticatorActivity
65+
val canLaunch = !preferences.onBoardingComplete && activity is AuthenticatorActivity
6666
if (canLaunch) {
6767
val intent = Intent(activity, FirstRunActivity::class.java)
6868
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
@@ -82,6 +82,8 @@ public final class AppPreferencesImpl implements AppPreferences {
8282
private static final String PREF__FOLDER_SORT_ORDER = "folder_sort_order";
8383
private static final String PREF__FOLDER_LAYOUT = "folder_layout";
8484

85+
private static final String PREF__ON_BOARDING_COMPLETE = "on_boarding_complete";
86+
8587
private static final String PREF__LOCK_TIMESTAMP = "lock_timestamp";
8688
private static final String PREF__SHOW_MEDIA_SCAN_NOTIFICATIONS = "show_media_scan_notifications";
8789
private static final String PREF__LOCK = SettingsActivity.PREFERENCE_LOCK;
@@ -746,6 +748,16 @@ public void setGlobalUploadPaused(boolean globalPausedState) {
746748
preferences.edit().putBoolean(PREF__GLOBAL_PAUSE_STATE, globalPausedState).apply();
747749
}
748750

751+
@Override
752+
public void setOnBoardingComplete(boolean isCompleted) {
753+
preferences.edit().putBoolean(PREF__ON_BOARDING_COMPLETE, isCompleted).apply();
754+
}
755+
756+
@Override
757+
public boolean getOnBoardingComplete() {
758+
return preferences.getBoolean(PREF__ON_BOARDING_COMPLETE, false);
759+
}
760+
749761
@Override
750762
public void setPdfZoomTipShownCount(int count) {
751763
preferences.edit().putInt(PREF__PDF_ZOOM_TIP_SHOWN, count).apply();

0 commit comments

Comments
 (0)