FirstRunActivity.kt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Bartosz Przybylski
  5. * @author Chris Narkiewicz
  6. * Copyright (C) 2015 Bartosz Przybylski
  7. * Copyright (C) 2015 ownCloud Inc.
  8. * Copyright (C) 2016 Nextcloud.
  9. * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
  10. *
  11. * This program is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  13. * License as published by the Free Software Foundation; either
  14. * version 3 of the License, or any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public
  22. * License along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. */
  24. package com.nextcloud.client.onboarding
  25. import android.accounts.AccountManager
  26. import android.content.Intent
  27. import android.content.res.Configuration
  28. import android.os.Bundle
  29. import android.view.View
  30. import android.view.ViewGroup
  31. import android.widget.LinearLayout
  32. import androidx.activity.OnBackPressedCallback
  33. import androidx.activity.result.ActivityResult
  34. import androidx.activity.result.ActivityResultLauncher
  35. import androidx.activity.result.contract.ActivityResultContracts
  36. import androidx.viewpager.widget.ViewPager
  37. import com.nextcloud.android.common.ui.theme.utils.ColorRole
  38. import com.nextcloud.client.account.UserAccountManager
  39. import com.nextcloud.client.appinfo.AppInfo
  40. import com.nextcloud.client.di.Injectable
  41. import com.nextcloud.client.preferences.AppPreferences
  42. import com.owncloud.android.BuildConfig
  43. import com.owncloud.android.R
  44. import com.owncloud.android.authentication.AuthenticatorActivity
  45. import com.owncloud.android.databinding.FirstRunActivityBinding
  46. import com.owncloud.android.features.FeatureItem
  47. import com.owncloud.android.ui.activity.BaseActivity
  48. import com.owncloud.android.ui.activity.FileDisplayActivity
  49. import com.owncloud.android.ui.adapter.FeaturesViewAdapter
  50. import com.owncloud.android.utils.DisplayUtils
  51. import com.owncloud.android.utils.theme.ViewThemeUtils
  52. import javax.inject.Inject
  53. /**
  54. * Activity displaying general feature after a fresh install.
  55. */
  56. class FirstRunActivity : BaseActivity(), ViewPager.OnPageChangeListener, Injectable {
  57. @JvmField
  58. @Inject
  59. var userAccountManager: UserAccountManager? = null
  60. @JvmField
  61. @Inject
  62. var preferences: AppPreferences? = null
  63. @JvmField
  64. @Inject
  65. var appInfo: AppInfo? = null
  66. @JvmField
  67. @Inject
  68. var onboarding: OnboardingService? = null
  69. @JvmField
  70. @Inject
  71. var viewThemeUtilsFactory: ViewThemeUtils.Factory? = null
  72. private var activityResult: ActivityResultLauncher<Intent>? = null
  73. private lateinit var binding: FirstRunActivityBinding
  74. private var defaultViewThemeUtils: ViewThemeUtils? = null
  75. override fun onCreate(savedInstanceState: Bundle?) {
  76. enableAccountHandling = false
  77. super.onCreate(savedInstanceState)
  78. applyDefaultTheme()
  79. binding = FirstRunActivityBinding.inflate(layoutInflater)
  80. setContentView(binding.root)
  81. val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
  82. setSlideshowSize(resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
  83. registerActivityResult()
  84. setupLoginButton()
  85. setupSignupButton(isProviderOrOwnInstallationVisible)
  86. setupHostOwnServerTextView(isProviderOrOwnInstallationVisible)
  87. deleteAccountAtFirstLaunch()
  88. setupFeaturesViewAdapter()
  89. handleOnBackPressed()
  90. }
  91. private fun applyDefaultTheme() {
  92. defaultViewThemeUtils = viewThemeUtilsFactory?.withPrimaryAsBackground()
  93. defaultViewThemeUtils?.platform?.themeStatusBar(this, ColorRole.PRIMARY)
  94. }
  95. private fun registerActivityResult() {
  96. activityResult =
  97. registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
  98. if (RESULT_OK == result.resultCode) {
  99. val data = result.data
  100. val accountName = data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
  101. val account = userAccountManager?.getAccountByName(accountName)
  102. if (account == null) {
  103. DisplayUtils.showSnackMessage(this, R.string.account_creation_failed)
  104. return@registerForActivityResult
  105. }
  106. userAccountManager?.setCurrentOwnCloudAccount(account.name)
  107. val i = Intent(this, FileDisplayActivity::class.java)
  108. i.action = FileDisplayActivity.RESTART
  109. i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  110. startActivity(i)
  111. finish()
  112. }
  113. }
  114. }
  115. private fun setupLoginButton() {
  116. defaultViewThemeUtils?.material?.colorMaterialButtonFilledOnPrimary(binding.login)
  117. binding.login.setOnClickListener {
  118. if (intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
  119. val authenticatorActivityIntent = getAuthenticatorActivityIntent(false)
  120. activityResult?.launch(authenticatorActivityIntent)
  121. } else {
  122. finish()
  123. }
  124. }
  125. }
  126. private fun setupSignupButton(isProviderOrOwnInstallationVisible: Boolean) {
  127. defaultViewThemeUtils?.material?.colorMaterialButtonOutlinedOnPrimary(binding.signup)
  128. binding.signup.visibility = if (isProviderOrOwnInstallationVisible) View.VISIBLE else View.GONE
  129. binding.signup.setOnClickListener {
  130. val authenticatorActivityIntent = getAuthenticatorActivityIntent(true)
  131. if (intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
  132. activityResult?.launch(authenticatorActivityIntent)
  133. } else {
  134. authenticatorActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
  135. startActivity(authenticatorActivityIntent)
  136. }
  137. }
  138. }
  139. private fun getAuthenticatorActivityIntent(extraUseProviderAsWebLogin: Boolean): Intent {
  140. val intent = Intent(this, AuthenticatorActivity::class.java)
  141. intent.putExtra(AuthenticatorActivity.EXTRA_USE_PROVIDER_AS_WEBLOGIN, extraUseProviderAsWebLogin)
  142. return intent
  143. }
  144. private fun setupHostOwnServerTextView(isProviderOrOwnInstallationVisible: Boolean) {
  145. defaultViewThemeUtils?.platform?.colorTextView(binding.hostOwnServer, ColorRole.ON_PRIMARY)
  146. binding.hostOwnServer.visibility = if (isProviderOrOwnInstallationVisible) View.VISIBLE else View.GONE
  147. if (isProviderOrOwnInstallationVisible) {
  148. binding.hostOwnServer.setOnClickListener {
  149. DisplayUtils.startLinkIntent(
  150. this,
  151. R.string.url_server_install
  152. )
  153. }
  154. }
  155. }
  156. // Sometimes, accounts are not deleted when you uninstall the application so we'll do it now
  157. private fun deleteAccountAtFirstLaunch() {
  158. if (onboarding?.isFirstRun == true) {
  159. userAccountManager?.removeAllAccounts()
  160. }
  161. }
  162. @Suppress("SpreadOperator")
  163. private fun setupFeaturesViewAdapter() {
  164. val featuresViewAdapter = FeaturesViewAdapter(supportFragmentManager, *firstRun)
  165. binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.count)
  166. binding.contentPanel.adapter = featuresViewAdapter
  167. binding.contentPanel.addOnPageChangeListener(this)
  168. }
  169. private fun handleOnBackPressed() {
  170. onBackPressedDispatcher.addCallback(
  171. this,
  172. object : OnBackPressedCallback(true) {
  173. override fun handleOnBackPressed() {
  174. onFinish()
  175. if (intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
  176. onBackPressedDispatcher.onBackPressed()
  177. } else {
  178. val intent = Intent(applicationContext, AuthenticatorActivity::class.java)
  179. intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
  180. intent.putExtra(EXTRA_EXIT, true)
  181. startActivity(intent)
  182. finish()
  183. }
  184. }
  185. }
  186. )
  187. }
  188. private fun setSlideshowSize(isLandscape: Boolean) {
  189. val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
  190. binding.buttonLayout.orientation = if (isLandscape) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
  191. val layoutParams: LinearLayout.LayoutParams = if (isProviderOrOwnInstallationVisible) {
  192. LinearLayout.LayoutParams(
  193. ViewGroup.LayoutParams.MATCH_PARENT,
  194. ViewGroup.LayoutParams.WRAP_CONTENT
  195. )
  196. } else {
  197. @Suppress("MagicNumber")
  198. LinearLayout.LayoutParams(
  199. ViewGroup.LayoutParams.MATCH_PARENT,
  200. DisplayUtils.convertDpToPixel(if (isLandscape) 100f else 150f, this)
  201. )
  202. }
  203. binding.bottomLayout.layoutParams = layoutParams
  204. }
  205. override fun onConfigurationChanged(newConfig: Configuration) {
  206. super.onConfigurationChanged(newConfig)
  207. setSlideshowSize(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
  208. }
  209. private fun onFinish() {
  210. preferences?.lastSeenVersionCode = BuildConfig.VERSION_CODE
  211. }
  212. override fun onStop() {
  213. onFinish()
  214. super.onStop()
  215. }
  216. override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
  217. // unused but to be implemented due to abstract parent
  218. }
  219. override fun onPageSelected(position: Int) {
  220. binding.progressIndicator.animateToStep(position + 1)
  221. }
  222. override fun onPageScrollStateChanged(state: Int) {
  223. // unused but to be implemented due to abstract parent
  224. }
  225. companion object {
  226. const val EXTRA_ALLOW_CLOSE = "ALLOW_CLOSE"
  227. const val EXTRA_EXIT = "EXIT"
  228. val firstRun: Array<FeatureItem>
  229. get() = arrayOf(
  230. FeatureItem(R.drawable.logo, R.string.first_run_1_text, R.string.empty, true, false),
  231. FeatureItem(R.drawable.first_run_files, R.string.first_run_2_text, R.string.empty, true, false),
  232. FeatureItem(R.drawable.first_run_groupware, R.string.first_run_3_text, R.string.empty, true, false),
  233. FeatureItem(R.drawable.first_run_talk, R.string.first_run_4_text, R.string.empty, true, false)
  234. )
  235. }
  236. }