LoginOptionsFragment.kt

package chat.rocket.android.authentication.loginoptions.ui

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.Activity
import android.content.Intent
import android.graphics.PorterDuff
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.widget.Button
import android.widget.LinearLayout
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.isVisible
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.domain.model.DeepLinkInfo
import chat.rocket.android.authentication.loginoptions.presentation.LoginOptionsPresenter
import chat.rocket.android.authentication.loginoptions.presentation.LoginOptionsView
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.util.extensions.*
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_SECRET
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN
import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent
import chat.rocket.android.webview.sso.ui.INTENT_SSO_TOKEN
import chat.rocket.android.webview.sso.ui.ssoWebViewIntent
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_authentication_login_options.*
import timber.log.Timber
import javax.inject.Inject

private const val SERVER_NAME = "server_name"
private const val STATE = "state"
private const val FACEBOOK_OAUTH_URL = "facebook_oauth_url"
private const val GITHUB_OAUTH_URL = "github_oauth_url"
private const val GOOGLE_OAUTH_URL = "google_oauth_url"
private const val LINKEDIN_OAUTH_URL = "linkedin_oauth_url"
private const val GITLAB_OAUTH_URL = "gitlab_oauth_url"
private const val WORDPRESS_OAUTH_URL = "wordpress_oauth_url"
private const val CAS_LOGIN_URL = "cas_login_url"
private const val CAS_TOKEN = "cas_token"
private const val CAS_SERVICE_NAME = "cas_service_name"
private const val CAS_SERVICE_NAME_TEXT_COLOR = "cas_service_name_text_color"
private const val CAS_SERVICE_BUTTON_COLOR = "cas_service_button_color"
private const val CUSTOM_OAUTH_URL = "custom_oauth_url"
private const val CUSTOM_OAUTH_SERVICE_NAME = "custom_oauth_service_name"
private const val CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR = "custom_oauth_service_name_text_color"
private const val CUSTOM_OAUTH_SERVICE_BUTTON_COLOR = "custom_oauth_service_button_color"
private const val SAML_URL = "saml_url"
private const val SAML_TOKEN = "saml_token"
private const val SAML_SERVICE_NAME = "saml_service_name"
private const val SAML_SERVICE_NAME_TEXT_COLOR = "saml_service_name_text_color"
private const val SAML_SERVICE_BUTTON_COLOR = "saml_service_button_color"
private const val TOTAL_SOCIAL_ACCOUNTS = "total_social_accounts"
private const val IS_LOGIN_FORM_ENABLED = "is_login_form_enabled"
private const val IS_NEW_ACCOUNT_CREATION_ENABLED = "is_new_account_creation_enabled"

internal const val REQUEST_CODE_FOR_OAUTH = 1
internal const val REQUEST_CODE_FOR_CAS = 2
internal const val REQUEST_CODE_FOR_SAML = 3

private const val DEFAULT_ANIMATION_DURATION = 400L

fun newInstance(
    serverName: String,
    state: String? = null,
    facebookOauthUrl: String? = null,
    githubOauthUrl: String? = null,
    googleOauthUrl: String? = null,
    linkedinOauthUrl: String? = null,
    gitlabOauthUrl: String? = null,
    wordpressOauthUrl: String? = null,
    casLoginUrl: String? = null,
    casToken: String? = null,
    casServiceName: String? = null,
    casServiceNameTextColor: Int = 0,
    casServiceButtonColor: Int = 0,
    customOauthUrl: String? = null,
    customOauthServiceName: String? = null,
    customOauthServiceNameTextColor: Int = 0,
    customOauthServiceButtonColor: Int = 0,
    samlUrl: String? = null,
    samlToken: String? = null,
    samlServiceName: String? = null,
    samlServiceNameTextColor: Int = 0,
    samlServiceButtonColor: Int = 0,
    totalSocialAccountsEnabled: Int = 0,
    isLoginFormEnabled: Boolean,
    isNewAccountCreationEnabled: Boolean,
    deepLinkInfo: DeepLinkInfo? = null
): Fragment = LoginOptionsFragment().apply {
    arguments = Bundle(23).apply {
        putString(SERVER_NAME, serverName)
        putString(STATE, state)
        putString(FACEBOOK_OAUTH_URL, facebookOauthUrl)
        putString(GITHUB_OAUTH_URL, githubOauthUrl)
        putString(GOOGLE_OAUTH_URL, googleOauthUrl)
        putString(LINKEDIN_OAUTH_URL, linkedinOauthUrl)
        putString(GITLAB_OAUTH_URL, gitlabOauthUrl)
        putString(WORDPRESS_OAUTH_URL, wordpressOauthUrl)
        putString(CAS_LOGIN_URL, casLoginUrl)
        putString(CAS_TOKEN, casToken)
        putString(CAS_SERVICE_NAME, casServiceName)
        putInt(CAS_SERVICE_NAME_TEXT_COLOR, casServiceNameTextColor)
        putInt(CAS_SERVICE_BUTTON_COLOR, casServiceButtonColor)
        putString(CUSTOM_OAUTH_URL, customOauthUrl)
        putString(CUSTOM_OAUTH_SERVICE_NAME, customOauthServiceName)
        putInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR, customOauthServiceNameTextColor)
        putInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR, customOauthServiceButtonColor)
        putString(SAML_URL, samlUrl)
        putString(SAML_TOKEN, samlToken)
        putString(SAML_SERVICE_NAME, samlServiceName)
        putInt(SAML_SERVICE_NAME_TEXT_COLOR, samlServiceNameTextColor)
        putInt(SAML_SERVICE_BUTTON_COLOR, samlServiceButtonColor)
        putInt(TOTAL_SOCIAL_ACCOUNTS, totalSocialAccountsEnabled)
        putBoolean(IS_LOGIN_FORM_ENABLED, isLoginFormEnabled)
        putBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED, isNewAccountCreationEnabled)
        putParcelable(
            chat.rocket.android.authentication.domain.model.DEEP_LINK_INFO_KEY,
            deepLinkInfo
        )
    }
}

class LoginOptionsFragment : Fragment(), LoginOptionsView {
    @Inject
    lateinit var presenter: LoginOptionsPresenter
    @Inject
    lateinit var analyticsManager: AnalyticsManager
    private var serverName: String? = null
    private var state: String? = null
    private var facebookOauthUrl: String? = null
    private var githubOauthUrl: String? = null
    private var googleOauthUrl: String? = null
    private var linkedinOauthUrl: String? = null
    private var gitlabOauthUrl: String? = null
    private var wordpressOauthUrl: String? = null
    private var casLoginUrl: String? = null
    private var casToken: String? = null
    private var casServiceName: String? = null
    private var casServiceNameTextColor: Int = 0
    private var casServiceButtonColor: Int = 0
    private var customOauthUrl: String? = null
    private var customOauthServiceName: String? = null
    private var customOauthServiceTextColor: Int = 0
    private var customOauthServiceButtonColor: Int = 0
    private var samlUrl: String? = null
    private var samlToken: String? = null
    private var samlServiceName: String? = null
    private var samlServiceTextColor: Int = 0
    private var samlServiceButtonColor: Int = 0
    private var totalSocialAccountsEnabled = 0
    private var isLoginFormEnabled = false
    private var isNewAccountCreationEnabled = false
    private var deepLinkInfo: DeepLinkInfo? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        AndroidSupportInjection.inject(this)

        arguments?.run {
            serverName = getString(SERVER_NAME)
            state = getString(STATE)
            facebookOauthUrl = getString(FACEBOOK_OAUTH_URL)
            githubOauthUrl = getString(GITHUB_OAUTH_URL)
            googleOauthUrl = getString(GOOGLE_OAUTH_URL)
            linkedinOauthUrl = getString(LINKEDIN_OAUTH_URL)
            gitlabOauthUrl = getString(GITLAB_OAUTH_URL)
            wordpressOauthUrl = getString(WORDPRESS_OAUTH_URL)
            casLoginUrl = getString(CAS_LOGIN_URL)
            casToken = getString(CAS_TOKEN)
            casServiceName = getString(CAS_SERVICE_NAME)
            casServiceNameTextColor = getInt(CAS_SERVICE_NAME_TEXT_COLOR)
            casServiceButtonColor = getInt(CAS_SERVICE_BUTTON_COLOR)
            customOauthUrl = getString(CUSTOM_OAUTH_URL)
            customOauthServiceName = getString(CUSTOM_OAUTH_SERVICE_NAME)
            customOauthServiceTextColor = getInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR)
            customOauthServiceButtonColor = getInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR)
            samlUrl = getString(SAML_URL)
            samlToken = getString(SAML_TOKEN)
            samlServiceName = getString(SAML_SERVICE_NAME)
            samlServiceTextColor = getInt(SAML_SERVICE_NAME_TEXT_COLOR)
            samlServiceButtonColor = getInt(SAML_SERVICE_BUTTON_COLOR)
            totalSocialAccountsEnabled = getInt(TOTAL_SOCIAL_ACCOUNTS)
            isLoginFormEnabled = getBoolean(IS_LOGIN_FORM_ENABLED)
            isNewAccountCreationEnabled = getBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED)
            deepLinkInfo =
                getParcelable(chat.rocket.android.authentication.domain.model.DEEP_LINK_INFO_KEY)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? = container?.inflate(R.layout.fragment_authentication_login_options)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupToolbar()
        setupAccounts()
        analyticsManager.logScreenView(ScreenViewEvent.LoginOptions)
        deepLinkInfo?.let { presenter.authenticateWithDeepLink(it) }
    }

    private fun setupToolbar() {
        with(activity as AuthenticationActivity) {
            this.clearLightStatusBar()
            toolbar.isVisible = true
            toolbar.title = serverName?.replace(getString(R.string.default_protocol), "")
        }
    }

    private fun setupAccounts() {
        setupSocialAccounts()
        setupCas()
        setupCustomOauth()
        setupSaml()
        setupAccountsView()
        setupLoginWithEmailView()
        setupCreateNewAccountView()
    }

    private fun setupSocialAccounts() {
        if (facebookOauthUrl != null && state != null) {
            setupFacebookButtonListener(facebookOauthUrl.toString(), state.toString())
            enableLoginByFacebook()
        }

        if (githubOauthUrl != null && state != null) {
            setupGithubButtonListener(githubOauthUrl.toString(), state.toString())
            enableLoginByGithub()
        }

        if (googleOauthUrl != null && state != null) {
            setupGoogleButtonListener(googleOauthUrl.toString(), state.toString())
            enableLoginByGoogle()
        }

        if (linkedinOauthUrl != null && state != null) {
            setupLinkedinButtonListener(linkedinOauthUrl.toString(), state.toString())
            enableLoginByLinkedin()
        }

        if (gitlabOauthUrl != null && state != null) {
            setupGitlabButtonListener(gitlabOauthUrl.toString(), state.toString())
            enableLoginByGitlab()
        }

        if (wordpressOauthUrl != null && state != null) {
            setupWordpressButtonListener(wordpressOauthUrl.toString(), state.toString())
            enableLoginByWordpress()
        }
    }

    private fun setupCas() {
        if (casLoginUrl != null && casToken != null && casServiceName != null) {
            addCasButton(
                casLoginUrl.toString(),
                casToken.toString(),
                casServiceName.toString(),
                casServiceNameTextColor,
                casServiceButtonColor
            )
        }
    }

    private fun setupCustomOauth() {
        if (customOauthUrl != null && state != null && customOauthServiceName != null) {
            addCustomOauthButton(
                customOauthUrl.toString(),
                state.toString(),
                customOauthServiceName.toString(),
                customOauthServiceTextColor,
                customOauthServiceButtonColor
            )
        }
    }

    private fun setupSaml() {
        if (samlUrl != null && samlToken != null && samlServiceName != null) {
            addSamlButton(
                samlUrl.toString(),
                samlToken.toString(),
                samlServiceName.toString(),
                samlServiceTextColor,
                samlServiceButtonColor
            )
        }
    }

    private fun setupAccountsView() {
        if (totalSocialAccountsEnabled > 0) {
            showAccountsView()
            if (totalSocialAccountsEnabled > 3) {
                setupExpandAccountsView()
            }
        }
    }

    private fun setupLoginWithEmailView() {
        if (isLoginFormEnabled) {
            showLoginWithEmailButton()
        }
    }

    private fun setupCreateNewAccountView() {
        if (isNewAccountCreationEnabled) {
            showCreateNewAccountButton()
        }
    }

    // OAuth Accounts.
    override fun enableLoginByFacebook() = enableAccountButton(button_facebook)

    override fun setupFacebookButtonListener(facebookOauthUrl: String, state: String) =
        setupButtonListener(button_facebook, facebookOauthUrl, state, REQUEST_CODE_FOR_OAUTH)

    override fun enableLoginByGithub() = enableAccountButton(button_github)

    override fun setupGithubButtonListener(githubUrl: String, state: String) =
        setupButtonListener(button_github, githubUrl, state, REQUEST_CODE_FOR_OAUTH)

    override fun enableLoginByGoogle() = enableAccountButton(button_google)

    override fun setupGoogleButtonListener(googleUrl: String, state: String) =
        setupButtonListener(button_google, googleUrl, state, REQUEST_CODE_FOR_OAUTH)

    override fun enableLoginByLinkedin() = enableAccountButton(button_linkedin)

    override fun setupLinkedinButtonListener(linkedinUrl: String, state: String) =
        setupButtonListener(button_linkedin, linkedinUrl, state, REQUEST_CODE_FOR_OAUTH)

    override fun enableLoginByGitlab() = enableAccountButton(button_gitlab)

    override fun setupGitlabButtonListener(gitlabUrl: String, state: String) =
        setupButtonListener(button_gitlab, gitlabUrl, state, REQUEST_CODE_FOR_OAUTH)

    override fun enableLoginByWordpress() = enableAccountButton(button_wordpress)

    override fun setupWordpressButtonListener(wordpressUrl: String, state: String) =
        setupButtonListener(button_wordpress, wordpressUrl, state, REQUEST_CODE_FOR_OAUTH)

    // CAS service account.
    override fun addCasButton(
        caslUrl: String,
        casToken: String,
        serviceName: String,
        serviceNameColor: Int,
        buttonColor: Int
    ) {
        val button = getCustomServiceButton(serviceName, serviceNameColor, buttonColor)
        setupButtonListener(button, caslUrl, casToken, REQUEST_CODE_FOR_CAS)
        accounts_container.addView(button)
    }

    // Custom OAuth account.
    override fun addCustomOauthButton(
        customOauthUrl: String,
        state: String,
        serviceName: String,
        serviceNameColor: Int,
        buttonColor: Int
    ) {
        val button = getCustomServiceButton(serviceName, serviceNameColor, buttonColor)
        setupButtonListener(button, customOauthUrl, state, REQUEST_CODE_FOR_OAUTH)
        accounts_container.addView(button)
    }

    // SAML account.
    override fun addSamlButton(
        samlUrl: String,
        samlToken: String,
        serviceName: String,
        serviceNameColor: Int,
        buttonColor: Int
    ) {
        val button = getCustomServiceButton(serviceName, serviceNameColor, buttonColor)
        setupButtonListener(button, samlUrl, samlToken, REQUEST_CODE_FOR_SAML)
        accounts_container.addView(button)
    }

    override fun showAccountsView() {
        ui {
            showThreeAccountsMethods()
            accounts_container.isVisible = true
        }
    }

    override fun setupExpandAccountsView() {
        ui {
            expand_more_accounts_container.isVisible = true
            var isAccountsCollapsed = true
            button_expand_collapse_accounts.setOnClickListener {
                isAccountsCollapsed = if (isAccountsCollapsed) {
                    button_expand_collapse_accounts.rotateBy(180F, DEFAULT_ANIMATION_DURATION)
                    expandAccountsView()
                    false
                } else {
                    button_expand_collapse_accounts.rotateBy(180F, DEFAULT_ANIMATION_DURATION)
                    collapseAccountsView()
                    true
                }
            }
        }
    }

    override fun showLoginWithEmailButton() {
        ui {
            button_login_with_email.setOnClickListener { presenter.toLoginWithEmail() }
            button_login_with_email.isVisible = true
        }
    }

    override fun showCreateNewAccountButton() {
        ui {
            button_create_an_account.setOnClickListener { presenter.toCreateAccount() }
            button_create_an_account.isVisible = true
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (resultCode == Activity.RESULT_OK && data != null) {
            when (requestCode) {
                REQUEST_CODE_FOR_OAUTH -> {
                    presenter.authenticateWithOauth(
                        data.getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN),
                        data.getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET)
                    )
                }
                REQUEST_CODE_FOR_CAS -> presenter.authenticateWithCas(
                    data.getStringExtra(INTENT_SSO_TOKEN)
                )
                REQUEST_CODE_FOR_SAML -> data.apply {
                    presenter.authenticateWithSaml(getStringExtra(INTENT_SSO_TOKEN))
                }
            }
        }
    }

    override fun showLoading() {
        ui {
            view_loading.isVisible = true
        }
    }

    override fun hideLoading() {
        ui {
            view_loading.isVisible = false
        }
    }

    override fun showMessage(resId: Int) {
        ui {
            showToast(resId)
        }
    }

    override fun showMessage(message: String) {
        ui {
            showToast(message)
        }
    }

    override fun showGenericErrorMessage() {
        showMessage(R.string.msg_generic_error)
    }

    private fun enableAccountButton(button: Button) {
        ui {
            button.isClickable = true
        }
    }

    private fun setupButtonListener(
        button: Button,
        accountUrl: String,
        argument: String,
        requestCode: Int
    ) {
        ui { activity ->
            button.setOnClickListener {
                when (requestCode) {
                    REQUEST_CODE_FOR_OAUTH -> startActivityForResult(
                        activity.oauthWebViewIntent(accountUrl, argument), REQUEST_CODE_FOR_OAUTH
                    )
                    REQUEST_CODE_FOR_CAS -> startActivityForResult(
                        activity.ssoWebViewIntent(accountUrl, argument), REQUEST_CODE_FOR_CAS
                    )
                    REQUEST_CODE_FOR_SAML -> startActivityForResult(
                        activity.ssoWebViewIntent(accountUrl, argument), REQUEST_CODE_FOR_SAML
                    )
                }

                activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
            }
        }
    }

    /**
     * Gets a stylized custom service button.
     */
    private fun getCustomServiceButton(
        buttonText: String,
        buttonTextColor: Int,
        buttonBgColor: Int
    ): Button {
        val params: LinearLayout.LayoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        )

        val marginTop = resources.getDimensionPixelSize(R.dimen.button_account_margin_top)
        params.setMargins(0, marginTop, 0, 0)

        val button = Button(
            ContextThemeWrapper(context, R.style.Authentication_Button),
            null,
            R.style.Authentication_Button
        )
        button.layoutParams = params
        button.text = buttonText
        button.setTextColor(buttonTextColor)
        button.background.setColorFilter(buttonBgColor, PorterDuff.Mode.MULTIPLY)

        return button
    }

    private fun showThreeAccountsMethods() {
        (0..accounts_container.childCount)
            .mapNotNull { accounts_container.getChildAt(it) as? Button }
            .filter { it.isClickable }
            .take(3)
            .forEach { it.isVisible = true }
    }

    private fun expandAccountsView() {
        val buttons = (0..accounts_container.childCount)
            .mapNotNull { accounts_container.getChildAt(it) as? Button }
            .filter { it.isClickable && !it.isVisible }
        val optionHeight = accounts_container.getChildAt(1).height +
            accounts_container.getChildAt(1).marginTop
        val collapsedHeight = accounts_container.height
        val expandedHeight = collapsedHeight + optionHeight * buttons.size

        with(ValueAnimator.ofInt(collapsedHeight, expandedHeight)) {
            addUpdateListener {
                val params = accounts_container.layoutParams
                params.height = animatedValue as Int
                accounts_container.layoutParams = params
            }
            addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animator: Animator) {
                    buttons.forEach {
                        it.isVisible = true
                        val anim = AlphaAnimation(0.0f, 1.0f)
                        anim.duration = DEFAULT_ANIMATION_DURATION
                        it.startAnimation(anim)
                    }
                }
            })
            setDuration(DEFAULT_ANIMATION_DURATION).start()
        }
    }

    private fun collapseAccountsView() {
        val buttons = (0..accounts_container.childCount)
            .mapNotNull { accounts_container.getChildAt(it) as? Button }
            .filter { it.isClickable && it.isVisible }
            .drop(3)
        val optionHeight = accounts_container.getChildAt(1).height +
            accounts_container.getChildAt(1).marginTop
        val expandedHeight = accounts_container.height
        val collapsedHeight = expandedHeight - optionHeight * buttons.size

        with(ValueAnimator.ofInt(expandedHeight, collapsedHeight)) {
            addUpdateListener {
                val params = accounts_container.layoutParams
                params.height = animatedValue as Int
                accounts_container.layoutParams = params
            }
            addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animator: Animator) {
                    buttons.forEach {
                        val anim = AlphaAnimation(1.0f, 0.0f)
                        anim.duration = DEFAULT_ANIMATION_DURATION
                        anim.setAnimationListener(object : Animation.AnimationListener {
                            override fun onAnimationStart(animation: Animation) {
                                Timber.d("Animation starts: $animation")
                            }

                            override fun onAnimationEnd(animation: Animation) {
                                it.isVisible = false
                            }

                            override fun onAnimationRepeat(animation: Animation) {
                                Timber.d("Animation repeats: $animation")
                            }
                        })
                        it.startAnimation(anim)
                    }
                }
            })
            setDuration(DEFAULT_ANIMATION_DURATION).start()
        }
    }
}