Social Login
And new custom homeserver completion (and remember history)
This commit is contained in:
parent
163c05d5cf
commit
03428ea9f5
@ -25,6 +25,7 @@ import androidx.work.WorkManager
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.BuildConfig
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.common.DaggerTestMatrixComponent
|
||||
@ -49,6 +50,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||
@Inject internal lateinit var olmManager: OlmManager
|
||||
@Inject internal lateinit var sessionManager: SessionManager
|
||||
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
|
||||
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
@ -71,6 +73,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
|
||||
fun rawService() = rawService
|
||||
|
||||
fun homeServerHistoryService() = homeServerHistoryService
|
||||
|
||||
fun legacySessionImporter(): LegacySessionImporter {
|
||||
return legacySessionImporter
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import androidx.work.WorkManager
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.BuildConfig
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
@ -47,6 +48,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||
@Inject internal lateinit var olmManager: OlmManager
|
||||
@Inject internal lateinit var sessionManager: SessionManager
|
||||
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
|
||||
|
||||
init {
|
||||
Monarchy.init(context)
|
||||
@ -65,6 +67,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
|
||||
fun rawService() = rawService
|
||||
|
||||
fun homeServerHistoryService() = homeServerHistoryService
|
||||
|
||||
fun legacySessionImporter(): LegacySessionImporter {
|
||||
return legacySessionImporter
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.auth
|
||||
|
||||
/**
|
||||
* A simple service to remember homeservers you already connected to.
|
||||
*/
|
||||
interface HomeServerHistoryService {
|
||||
|
||||
fun getKnownServersUrls(): List<String>
|
||||
|
||||
fun addHomeServerToHistory(url: String)
|
||||
|
||||
fun clearHistory()
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.auth.data
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Parcelize
|
||||
data class IdentityProvider(
|
||||
/**
|
||||
* The id field would be opaque with the accepted characters matching unreserved URI characters as defined in RFC3986
|
||||
* - this was chosen to avoid having to encode special characters in the URL. Max length 128.
|
||||
*/
|
||||
@Json(name = "id") val id: String,
|
||||
/**
|
||||
* The name field should be the human readable string intended for printing by the client.
|
||||
* */
|
||||
@Json(name = "name") val name: String?,
|
||||
/**
|
||||
* The icon field is the only optional field and should point to an icon representing the IdP.
|
||||
* If present then it must be an HTTPS URL to an image resource.
|
||||
* This should be hosted by the homeserver service provider to not leak the client's IP address unnecessarily.
|
||||
*/
|
||||
@Json(name = "icon") val icon: String?
|
||||
) : Parcelable
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.auth.data
|
||||
sealed class LoginFlowResult {
|
||||
data class Success(
|
||||
val supportedLoginTypes: List<String>,
|
||||
val ssoIdentityProviders: List<IdentityProvider>?,
|
||||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String,
|
||||
val isOutdatedHomeserver: Boolean
|
||||
|
@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
@ -278,6 +279,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
}
|
||||
return LoginFlowResult.Success(
|
||||
loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
||||
loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.identityProvider,
|
||||
versions.isLoginAndRegistrationSupportedBySdk(),
|
||||
homeServerUrl,
|
||||
!versions.isSupportedBySdk()
|
||||
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.auth
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntity
|
||||
import org.matrix.android.sdk.internal.di.GlobalDatabase
|
||||
import javax.inject.Inject
|
||||
|
||||
class DefaultHomeServerHistoryService @Inject constructor(
|
||||
@GlobalDatabase private val monarchy: Monarchy
|
||||
) : HomeServerHistoryService {
|
||||
|
||||
override fun getKnownServersUrls(): List<String> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ realm ->
|
||||
realm.where<KnownServerUrlEntity>()
|
||||
},
|
||||
{ it.url }
|
||||
)
|
||||
}
|
||||
|
||||
override fun addHomeServerToHistory(url: String) {
|
||||
monarchy.writeAsync { realm ->
|
||||
KnownServerUrlEntity(url).let {
|
||||
realm.insertOrUpdate(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearHistory() {
|
||||
monarchy.runTransactionSync { it.where<KnownServerUrlEntity>().findAll().deleteAllFromRealm() }
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.auth.data.IdentityProvider
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class LoginFlowResponse(
|
||||
@ -34,5 +35,13 @@ internal data class LoginFlow(
|
||||
* The login type. This is supplied as the type when logging in.
|
||||
*/
|
||||
@Json(name = "type")
|
||||
val type: String?
|
||||
val type: String?,
|
||||
|
||||
/**
|
||||
* Augments m.login.sso flow discovery definition to include metadata on the supported IDPs
|
||||
* the client can show a button for each of the supported providers
|
||||
* See MSC #2858
|
||||
*/
|
||||
@Json(name = "identity_providers")
|
||||
val identityProvider: List<IdentityProvider>?
|
||||
)
|
||||
|
@ -51,6 +51,18 @@ data class RegistrationFlowResponse(
|
||||
* The information that the client will need to know in order to use a given type of authentication.
|
||||
* For each login stage type presented, that type may be present as a key in this dictionary.
|
||||
* For example, the public key of reCAPTCHA stage could be given here.
|
||||
* other example
|
||||
* "params": {
|
||||
* "m.login.sso": {
|
||||
* "identity_providers": [
|
||||
* {
|
||||
* "id": "google",
|
||||
* "name": "Google",
|
||||
* "icon": "https://..."
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@Json(name = "params")
|
||||
val params: JsonDict? = null
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class KnownServerUrlEntity(
|
||||
@PrimaryKey
|
||||
var url: String = ""
|
||||
) : RealmObject() {
|
||||
companion object
|
||||
}
|
@ -25,6 +25,7 @@ import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.auth.AuthModule
|
||||
@ -62,6 +63,8 @@ internal interface MatrixComponent {
|
||||
|
||||
fun rawService(): RawService
|
||||
|
||||
fun homeServerHistoryService(): HomeServerHistoryService
|
||||
|
||||
fun context(): Context
|
||||
|
||||
fun matrixConfiguration(): MatrixConfiguration
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.raw
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntityFields
|
||||
import timber.log.Timber
|
||||
|
||||
internal object GlobalRealmMigration : RealmMigration {
|
||||
|
||||
// Current schema version
|
||||
const val SCHEMA_VERSION = 1L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
realm.schema.create("KnownServerUrlEntity")
|
||||
.addField(KnownServerUrlEntityFields.URL, String::class.java)
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
package org.matrix.android.sdk.internal.raw
|
||||
|
||||
import io.realm.annotations.RealmModule
|
||||
import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
||||
|
||||
/**
|
||||
@ -24,6 +25,7 @@ import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
classes = [
|
||||
RawCacheEntity::class
|
||||
RawCacheEntity::class,
|
||||
KnownServerUrlEntity::class
|
||||
])
|
||||
internal class GlobalRealmModule
|
||||
|
@ -24,7 +24,9 @@ import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.realm.RealmConfiguration
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.internal.auth.DefaultHomeServerHistoryService
|
||||
import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
||||
import org.matrix.android.sdk.internal.di.GlobalDatabase
|
||||
import org.matrix.android.sdk.internal.di.MatrixScope
|
||||
@ -57,6 +59,8 @@ internal abstract class RawModule {
|
||||
realmKeysUtils.configureEncryption(this, DB_ALIAS)
|
||||
}
|
||||
.name("matrix-sdk-global.realm")
|
||||
.schemaVersion(GlobalRealmMigration.SCHEMA_VERSION)
|
||||
.migration(GlobalRealmMigration)
|
||||
.modules(GlobalRealmModule())
|
||||
.build()
|
||||
}
|
||||
@ -77,4 +81,7 @@ internal abstract class RawModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bindCleanRawCacheTask(task: DefaultCleanRawCacheTask): CleanRawCacheTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ import im.vector.app.features.login.LoginResetPasswordSuccessFragment
|
||||
import im.vector.app.features.login.LoginServerSelectionFragment
|
||||
import im.vector.app.features.login.LoginServerUrlFormFragment
|
||||
import im.vector.app.features.login.LoginSignUpSignInSelectionFragment
|
||||
import im.vector.app.features.login.LoginSignUpSignInSsoFragment
|
||||
import im.vector.app.features.login.LoginSplashFragment
|
||||
import im.vector.app.features.login.LoginWaitForEmailFragment
|
||||
import im.vector.app.features.login.LoginWebFragment
|
||||
@ -229,11 +228,11 @@ interface FragmentModule {
|
||||
@IntoMap
|
||||
@FragmentKey(LoginSignUpSignInSelectionFragment::class)
|
||||
fun bindLoginSignUpSignInSelectionFragment(fragment: LoginSignUpSignInSelectionFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(LoginSignUpSignInSsoFragment::class)
|
||||
fun bindLoginSignUpSignInSsoFragment(fragment: LoginSignUpSignInSsoFragment): Fragment
|
||||
//
|
||||
// @Binds
|
||||
// @IntoMap
|
||||
// @FragmentKey(LoginSignUpSignInSsoFragment::class)
|
||||
// fun bindLoginSignUpSignInSsoFragment(fragment: LoginSignUpSignInSsoFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
|
@ -59,6 +59,7 @@ import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Singleton
|
||||
@ -127,6 +128,8 @@ interface VectorComponent {
|
||||
|
||||
fun rawService(): RawService
|
||||
|
||||
fun homeServerHistoryService(): HomeServerHistoryService
|
||||
|
||||
fun bugReporter(): BugReporter
|
||||
|
||||
fun vectorUncaughtExceptionHandler(): VectorUncaughtExceptionHandler
|
||||
|
@ -33,6 +33,7 @@ import im.vector.app.features.ui.SharedPreferencesUiStateRepository
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
@ -85,6 +86,12 @@ abstract class VectorModule {
|
||||
fun providesRawService(matrix: Matrix): RawService {
|
||||
return matrix.rawService()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService {
|
||||
return matrix.homeServerHistoryService()
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
@ -18,6 +18,7 @@ package im.vector.app.features.login
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.IdentityProvider
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.internal.network.ssl.Fingerprint
|
||||
|
||||
@ -60,7 +61,7 @@ sealed class LoginAction : VectorViewModelAction {
|
||||
object ResetResetPassword : ResetAction()
|
||||
|
||||
// For the soft logout case
|
||||
data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String) : LoginAction()
|
||||
data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String, val identityProvider: List<IdentityProvider>?) : LoginAction()
|
||||
|
||||
data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction()
|
||||
|
||||
|
@ -112,7 +112,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||
|
||||
private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
|
||||
when (loginViewEvents) {
|
||||
is LoginViewEvents.RegistrationFlowResult -> {
|
||||
is LoginViewEvents.RegistrationFlowResult -> {
|
||||
// Check that all flows are supported by the application
|
||||
if (loginViewEvents.flowResult.missingStages.any { !it.isSupported() }) {
|
||||
// Display a popup to propose use web fallback
|
||||
@ -133,7 +133,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||
}
|
||||
}
|
||||
}
|
||||
is LoginViewEvents.OutdatedHomeserver -> {
|
||||
is LoginViewEvents.OutdatedHomeserver -> {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.login_error_outdated_homeserver_title)
|
||||
.setMessage(R.string.login_error_outdated_homeserver_warning_content)
|
||||
@ -141,7 +141,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||
.show()
|
||||
Unit
|
||||
}
|
||||
is LoginViewEvents.OpenServerSelection ->
|
||||
is LoginViewEvents.OpenServerSelection ->
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginServerSelectionFragment::class.java,
|
||||
option = { ft ->
|
||||
@ -153,28 +153,24 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||
// TODO Disabled because it provokes a flickering
|
||||
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
||||
})
|
||||
is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents)
|
||||
is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents)
|
||||
is LoginViewEvents.OnLoginFlowRetrieved ->
|
||||
is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents)
|
||||
is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents)
|
||||
is LoginViewEvents.OnLoginFlowRetrieved ->
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
if (loginViewEvents.isSso) {
|
||||
LoginSignUpSignInSsoFragment::class.java
|
||||
} else {
|
||||
LoginSignUpSignInSelectionFragment::class.java
|
||||
},
|
||||
LoginSignUpSignInSelectionFragment::class.java,
|
||||
option = commonOption)
|
||||
is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents)
|
||||
is LoginViewEvents.OnForgetPasswordClicked ->
|
||||
is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents)
|
||||
is LoginViewEvents.OnForgetPasswordClicked ->
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginResetPasswordFragment::class.java,
|
||||
option = commonOption)
|
||||
is LoginViewEvents.OnResetPasswordSendThreePidDone -> {
|
||||
is LoginViewEvents.OnResetPasswordSendThreePidDone -> {
|
||||
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginResetPasswordMailConfirmationFragment::class.java,
|
||||
option = commonOption)
|
||||
}
|
||||
is LoginViewEvents.OnResetPasswordMailConfirmationSuccess -> {
|
||||
is LoginViewEvents.OnResetPasswordMailConfirmationSuccess -> {
|
||||
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginResetPasswordSuccessFragment::class.java,
|
||||
@ -184,20 +180,20 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||
// Go back to the login fragment
|
||||
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
||||
}
|
||||
is LoginViewEvents.OnSendEmailSuccess ->
|
||||
is LoginViewEvents.OnSendEmailSuccess ->
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginWaitForEmailFragment::class.java,
|
||||
LoginWaitForEmailFragmentArgument(loginViewEvents.email),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
is LoginViewEvents.OnSendMsisdnSuccess ->
|
||||
is LoginViewEvents.OnSendMsisdnSuccess ->
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginGenericTextInputFormFragment::class.java,
|
||||
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
is LoginViewEvents.Failure,
|
||||
is LoginViewEvents.Loading ->
|
||||
is LoginViewEvents.Loading ->
|
||||
// This is handled by the Fragments
|
||||
Unit
|
||||
}.exhaustive
|
||||
@ -234,25 +230,26 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||
when (loginViewEvents.serverType) {
|
||||
ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
|
||||
ServerType.EMS,
|
||||
ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginServerUrlFormFragment::class.java,
|
||||
option = commonOption)
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSignModeSelected(loginViewEvents: LoginViewEvents.OnSignModeSelected) = withState(loginViewModel) { state ->
|
||||
// state.signMode could not be ready yet. So use value from the ViewEvent
|
||||
when (loginViewEvents.signMode) {
|
||||
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
||||
SignMode.SignUp -> {
|
||||
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
||||
SignMode.SignUp -> {
|
||||
// This is managed by the LoginViewEvents
|
||||
}
|
||||
SignMode.SignIn -> {
|
||||
SignMode.SignIn -> {
|
||||
// It depends on the LoginMode
|
||||
when (state.loginMode) {
|
||||
LoginMode.Unknown,
|
||||
LoginMode.Sso -> error("Developer error")
|
||||
is LoginMode.Sso -> error("Developer error")
|
||||
is LoginMode.SsoAndPassword,
|
||||
LoginMode.Password -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginFragment::class.java,
|
||||
tag = FRAGMENT_LOGIN_TAG,
|
||||
@ -331,17 +328,17 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||
LoginCaptchaFragmentArgument(stage.publicKey),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
is Stage.Email -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
is Stage.Email -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginGenericTextInputFormFragment::class.java,
|
||||
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
is Stage.Msisdn -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
is Stage.Msisdn -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginGenericTextInputFormFragment::class.java,
|
||||
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
is Stage.Terms -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
is Stage.Terms -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginTermsFragment::class.java,
|
||||
LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(getString(R.string.resources_language))),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
@ -350,6 +347,10 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun configure(toolbar: Toolbar) {
|
||||
configureToolbar(toolbar)
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import io.reactivex.Observable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.android.synthetic.main.fragment_login.*
|
||||
import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||
@ -79,15 +80,17 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
private fun setupAutoFill(state: LoginViewState) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
when (state.signMode) {
|
||||
SignMode.Unknown -> error("developer error")
|
||||
SignMode.SignUp -> {
|
||||
SignMode.Unknown -> error("developer error")
|
||||
SignMode.SignUp -> {
|
||||
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
|
||||
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
|
||||
loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_UP
|
||||
}
|
||||
SignMode.SignIn,
|
||||
SignMode.SignInWithMatrixId -> {
|
||||
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
|
||||
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
|
||||
loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_IN
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
@ -142,9 +145,9 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
loginPasswordNotice.isVisible = true
|
||||
} else {
|
||||
val resId = when (state.signMode) {
|
||||
SignMode.Unknown -> error("developer error")
|
||||
SignMode.SignUp -> R.string.login_signup_to
|
||||
SignMode.SignIn -> R.string.login_connect_to
|
||||
SignMode.Unknown -> error("developer error")
|
||||
SignMode.SignUp -> R.string.login_signup_to
|
||||
SignMode.SignIn -> R.string.login_connect_to
|
||||
SignMode.SignInWithMatrixId -> R.string.login_connect_to
|
||||
}
|
||||
|
||||
@ -155,20 +158,28 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
||||
loginNotice.text = getString(R.string.login_server_matrix_org_text)
|
||||
}
|
||||
ServerType.EMS -> {
|
||||
ServerType.EMS -> {
|
||||
loginServerIcon.isVisible = true
|
||||
loginServerIcon.setImageResource(R.drawable.ic_logo_element_matrix_services)
|
||||
loginTitle.text = getString(resId, "Element Matrix Services")
|
||||
loginNotice.text = getString(R.string.login_server_modular_text)
|
||||
}
|
||||
ServerType.Other -> {
|
||||
ServerType.Other -> {
|
||||
loginServerIcon.isVisible = false
|
||||
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
||||
loginNotice.text = getString(R.string.login_server_other_text)
|
||||
}
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
}
|
||||
loginPasswordNotice.isVisible = false
|
||||
|
||||
if (state.loginMode is LoginMode.SsoAndPassword) {
|
||||
loginSocialLoginContainer.isVisible = true
|
||||
loginSocialLoginButtons.identityProviders = state.loginMode.identityProviders
|
||||
} else {
|
||||
loginSocialLoginContainer.isVisible = false
|
||||
loginSocialLoginButtons.identityProviders = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,7 +268,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
passwordShown = false
|
||||
renderPasswordField()
|
||||
}
|
||||
is Fail -> {
|
||||
is Fail -> {
|
||||
val error = state.asyncLoginAction.error
|
||||
if (error is Failure.ServerError
|
||||
&& error.error.code == MatrixError.M_FORBIDDEN
|
||||
|
@ -16,9 +16,31 @@
|
||||
|
||||
package im.vector.app.features.login
|
||||
|
||||
enum class LoginMode {
|
||||
Unknown,
|
||||
Password,
|
||||
Sso,
|
||||
Unsupported
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import org.matrix.android.sdk.api.auth.data.IdentityProvider
|
||||
|
||||
sealed class LoginMode : Parcelable
|
||||
/** because persist state */ {
|
||||
@Parcelize object Unknown : LoginMode()
|
||||
@Parcelize object Password : LoginMode()
|
||||
@Parcelize data class Sso(val identityProviders: List<IdentityProvider>?) : LoginMode()
|
||||
@Parcelize data class SsoAndPassword(val identityProviders: List<IdentityProvider>?) : LoginMode()
|
||||
@Parcelize object Unsupported : LoginMode()
|
||||
}
|
||||
|
||||
fun LoginMode.ssoProviders() : List<IdentityProvider>? {
|
||||
return when (this) {
|
||||
is LoginMode.Sso -> identityProviders
|
||||
is LoginMode.SsoAndPassword -> identityProviders
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun LoginMode.hasSso() : Boolean {
|
||||
return when (this) {
|
||||
is LoginMode.Sso -> true
|
||||
is LoginMode.SsoAndPassword -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
|
||||
|
||||
if (state.loginMode != LoginMode.Unknown) {
|
||||
// LoginFlow for matrix.org has been retrieved
|
||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode == LoginMode.Sso)))
|
||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode is LoginMode.Sso)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,10 @@ import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.core.view.isVisible
|
||||
import butterknife.OnClick
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
@ -55,6 +57,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
||||
|
||||
loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
loginServerUrlFormHomeServerUrl.dismissDropDown()
|
||||
submit()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
@ -81,6 +84,13 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
||||
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice)
|
||||
}
|
||||
}
|
||||
val completions = state.knownCustomHomeServersUrls
|
||||
loginServerUrlFormHomeServerUrl.setAdapter(ArrayAdapter(requireContext(), android.R.layout.select_dialog_item,
|
||||
completions
|
||||
))
|
||||
loginServerUrlFormHomeServerUrlTil.endIconMode = TextInputLayout.END_ICON_DROPDOWN_MENU
|
||||
.takeIf { completions.isNotEmpty() }
|
||||
?: TextInputLayout.END_ICON_NONE
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginServerUrlFormLearnMore)
|
||||
@ -105,7 +115,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
||||
loginServerUrlFormHomeServerUrlTil.error = getString(R.string.login_error_invalid_home_server)
|
||||
}
|
||||
else -> {
|
||||
loginServerUrlFormHomeServerUrl.setText(serverUrl)
|
||||
loginServerUrlFormHomeServerUrl.setText(serverUrl, false /* to avoid completion dialog flicker*/)
|
||||
loginViewModel.handle(LoginAction.UpdateHomeServer(serverUrl))
|
||||
}
|
||||
}
|
||||
@ -131,7 +141,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
||||
|
||||
if (state.loginMode != LoginMode.Unknown) {
|
||||
// The home server url is valid
|
||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode == LoginMode.Sso)))
|
||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode is LoginMode.Sso)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,21 +16,36 @@
|
||||
|
||||
package im.vector.app.features.login
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabsClient
|
||||
import androidx.browser.customtabs.CustomTabsServiceConnection
|
||||
import androidx.browser.customtabs.CustomTabsSession
|
||||
import androidx.core.view.isVisible
|
||||
import butterknife.OnClick
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
|
||||
import org.matrix.android.sdk.api.auth.data.IdentityProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* In this screen, the user is asked to sign up or to sign in to the homeserver
|
||||
*/
|
||||
open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
|
||||
// Map of sso urls by providers if any
|
||||
private var ssoUrls = emptyMap<String?, String>().toMutableMap()
|
||||
|
||||
private var customTabsServiceConnection: CustomTabsServiceConnection? = null
|
||||
private var customTabsClient: CustomTabsClient? = null
|
||||
private var customTabsSession: CustomTabsSession? = null
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_login_signup_signin_selection
|
||||
|
||||
protected fun setupUi(state: LoginViewState) {
|
||||
private fun setupUi(state: LoginViewState) {
|
||||
when (state.serverType) {
|
||||
ServerType.MatrixOrg -> {
|
||||
loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
|
||||
@ -51,16 +66,110 @@ open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLo
|
||||
}
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
}
|
||||
|
||||
val identityProviders = state.loginMode.ssoProviders()
|
||||
if (state.loginMode.hasSso() && identityProviders.isNullOrEmpty().not()) {
|
||||
loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
||||
loginSignupSigninSocialLoginButtons.identityProviders = identityProviders
|
||||
loginSignupSigninSocialLoginButtons.listener = object: SocialLoginButtonsView.InteractionListener {
|
||||
override fun onProviderSelected(id: IdentityProvider) {
|
||||
ssoUrls[id.id]?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loginSignupSigninSignInSocialLoginContainer.isVisible = false
|
||||
loginSignupSigninSocialLoginButtons.identityProviders = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
loginSignupSigninSubmit.text = getString(R.string.login_signup)
|
||||
loginSignupSigninSignIn.isVisible = true
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() }
|
||||
if (hasSSO) {
|
||||
val packageName = CustomTabsClient.getPackageName(requireContext(), null)
|
||||
|
||||
// packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device
|
||||
if (packageName != null) {
|
||||
customTabsServiceConnection = object : CustomTabsServiceConnection() {
|
||||
override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
|
||||
customTabsClient = client
|
||||
.also { it.warmup(0L) }
|
||||
|
||||
// prefetch urls
|
||||
prefetchSsoUrls()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
}
|
||||
}
|
||||
.also {
|
||||
CustomTabsClient.bindCustomTabsService(
|
||||
requireContext(),
|
||||
// Despite the API, packageName cannot be null
|
||||
packageName,
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() }
|
||||
if (hasSSO) {
|
||||
customTabsServiceConnection?.let { requireContext().unbindService(it) }
|
||||
customTabsServiceConnection = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun prefetchSsoUrls() = withState(loginViewModel) { state ->
|
||||
val providers = state.loginMode.ssoProviders()
|
||||
if (providers.isNullOrEmpty()) {
|
||||
state.getSsoUrl(null).let {
|
||||
ssoUrls[null] = it
|
||||
prefetchUrl(it)
|
||||
}
|
||||
} else {
|
||||
providers.forEach { identityProvider ->
|
||||
state.getSsoUrl(identityProvider.id).let {
|
||||
ssoUrls[identityProvider.id] = it
|
||||
// we don't prefetch for privacy reasons
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prefetchUrl(url: String) {
|
||||
if (customTabsSession == null) {
|
||||
customTabsSession = customTabsClient?.newSession(null)
|
||||
}
|
||||
|
||||
customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null)
|
||||
}
|
||||
|
||||
private fun setupButtons(state: LoginViewState) {
|
||||
when (state.loginMode) {
|
||||
is LoginMode.Sso -> {
|
||||
// change to only one button that is sign in with sso
|
||||
loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
|
||||
loginSignupSigninSignIn.isVisible = false
|
||||
}
|
||||
else -> {
|
||||
loginSignupSigninSubmit.text = getString(R.string.login_signup)
|
||||
loginSignupSigninSignIn.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginSignupSigninSubmit)
|
||||
open fun submit() {
|
||||
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
|
||||
fun submit() = withState(loginViewModel) { state ->
|
||||
if (state.loginMode is LoginMode.Sso) {
|
||||
ssoUrls[null]?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) }
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginSignupSigninSignIn)
|
||||
@ -74,6 +183,6 @@ open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLo
|
||||
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
setupUi(state)
|
||||
setupButtons()
|
||||
setupButtons(state)
|
||||
}
|
||||
}
|
||||
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.login
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabsClient
|
||||
import androidx.browser.customtabs.CustomTabsServiceConnection
|
||||
import androidx.browser.customtabs.CustomTabsSession
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* In this screen, the user is asked to sign up or to sign in using SSO
|
||||
* This Fragment binds a CustomTabsServiceConnection if available, then prefetch the SSO url, as it will be likely to be opened.
|
||||
*/
|
||||
open class LoginSignUpSignInSsoFragment @Inject constructor() : LoginSignUpSignInSelectionFragment() {
|
||||
|
||||
private var ssoUrl: String? = null
|
||||
private var customTabsServiceConnection: CustomTabsServiceConnection? = null
|
||||
private var customTabsClient: CustomTabsClient? = null
|
||||
private var customTabsSession: CustomTabsSession? = null
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
val packageName = CustomTabsClient.getPackageName(requireContext(), null)
|
||||
|
||||
// packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device
|
||||
if (packageName != null) {
|
||||
customTabsServiceConnection = object : CustomTabsServiceConnection() {
|
||||
override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
|
||||
customTabsClient = client
|
||||
.also { it.warmup(0L) }
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
}
|
||||
}
|
||||
.also {
|
||||
CustomTabsClient.bindCustomTabsService(
|
||||
requireContext(),
|
||||
// Despite the API, packageName cannot be null
|
||||
packageName,
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prefetchUrl(url: String) {
|
||||
if (ssoUrl != null) return
|
||||
|
||||
ssoUrl = url
|
||||
if (customTabsSession == null) {
|
||||
customTabsSession = customTabsClient?.newSession(null)
|
||||
}
|
||||
|
||||
customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
customTabsServiceConnection?.let { requireContext().unbindService(it) }
|
||||
customTabsServiceConnection = null
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
|
||||
loginSignupSigninSignIn.isVisible = false
|
||||
}
|
||||
|
||||
override fun submit() {
|
||||
ssoUrl?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) }
|
||||
}
|
||||
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
setupUi(state)
|
||||
setupButtons()
|
||||
prefetchUrl(state.getSsoUrl())
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.extensions.configureAndStart
|
||||
@ -38,6 +39,7 @@ import im.vector.app.core.utils.ensureTrailingSlash
|
||||
import im.vector.app.features.signout.soft.SoftLogoutActivity
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
@ -63,7 +65,8 @@ class LoginViewModel @AssistedInject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
|
||||
private val reAuthHelper: ReAuthHelper,
|
||||
private val stringProvider: StringProvider
|
||||
private val stringProvider: StringProvider,
|
||||
private val homeServerHistoryService: HomeServerHistoryService
|
||||
) : VectorViewModel<LoginViewState, LoginAction, LoginViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
@ -71,12 +74,21 @@ class LoginViewModel @AssistedInject constructor(
|
||||
fun create(initialState: LoginViewState): LoginViewModel
|
||||
}
|
||||
|
||||
init {
|
||||
if (BuildConfig.DEBUG) {
|
||||
homeServerHistoryService.addHomeServerToHistory("http://10.0.2.2:8080")
|
||||
}
|
||||
setState {
|
||||
copy(knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls())
|
||||
}
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<LoginViewModel, LoginViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: LoginViewState): LoginViewModel? {
|
||||
return when (val activity: FragmentActivity = (viewModelContext as ActivityViewModelContext).activity()) {
|
||||
is LoginActivity -> activity.loginViewModelFactory.create(state)
|
||||
is LoginActivity -> activity.loginViewModelFactory.create(state)
|
||||
is SoftLogoutActivity -> activity.loginViewModelFactory.create(state)
|
||||
else -> error("Invalid Activity")
|
||||
}
|
||||
@ -108,20 +120,20 @@ class LoginViewModel @AssistedInject constructor(
|
||||
|
||||
override fun handle(action: LoginAction) {
|
||||
when (action) {
|
||||
is LoginAction.UpdateServerType -> handleUpdateServerType(action)
|
||||
is LoginAction.UpdateSignMode -> handleUpdateSignMode(action)
|
||||
is LoginAction.InitWith -> handleInitWith(action)
|
||||
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action }
|
||||
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
|
||||
is LoginAction.LoginWithToken -> handleLoginWithToken(action)
|
||||
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||
is LoginAction.ResetPassword -> handleResetPassword(action)
|
||||
is LoginAction.UpdateServerType -> handleUpdateServerType(action)
|
||||
is LoginAction.UpdateSignMode -> handleUpdateSignMode(action)
|
||||
is LoginAction.InitWith -> handleInitWith(action)
|
||||
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action }
|
||||
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
|
||||
is LoginAction.LoginWithToken -> handleLoginWithToken(action)
|
||||
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||
is LoginAction.ResetPassword -> handleResetPassword(action)
|
||||
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
|
||||
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
||||
is LoginAction.ResetAction -> handleResetAction(action)
|
||||
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
||||
is LoginAction.ResetAction -> handleResetAction(action)
|
||||
is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
|
||||
is LoginAction.UserAcceptCertificate -> handleUserAcceptCertificate(action)
|
||||
is LoginAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
|
||||
is LoginAction.UserAcceptCertificate -> handleUserAcceptCertificate(action)
|
||||
is LoginAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@ -129,11 +141,13 @@ class LoginViewModel @AssistedInject constructor(
|
||||
// It happen when we get the login flow, or during direct authentication.
|
||||
// So alter the homeserver config and retrieve again the login flow
|
||||
when (val finalLastAction = lastAction) {
|
||||
is LoginAction.UpdateHomeServer ->
|
||||
is LoginAction.UpdateHomeServer -> {
|
||||
rememberHomeServer(finalLastAction.homeServerUrl)
|
||||
currentHomeServerConnectionConfig
|
||||
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
||||
?.let { getLoginFlow(it) }
|
||||
is LoginAction.LoginOrRegister ->
|
||||
}
|
||||
is LoginAction.LoginOrRegister ->
|
||||
handleDirectLogin(
|
||||
finalLastAction,
|
||||
HomeServerConnectionConfig.Builder()
|
||||
@ -145,6 +159,13 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun rememberHomeServer(homeServerUrl: String) {
|
||||
homeServerHistoryService.addHomeServerToHistory(homeServerUrl)
|
||||
setState {
|
||||
copy(knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls())
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
|
||||
val safeLoginWizard = loginWizard
|
||||
|
||||
@ -184,7 +205,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
setState {
|
||||
copy(
|
||||
signMode = SignMode.SignIn,
|
||||
loginMode = LoginMode.Sso,
|
||||
loginMode = LoginMode.Sso(action.identityProvider),
|
||||
homeServerUrl = action.homeServerUrl,
|
||||
deviceId = action.deviceId
|
||||
)
|
||||
@ -193,14 +214,14 @@ class LoginViewModel @AssistedInject constructor(
|
||||
|
||||
private fun handleRegisterAction(action: LoginAction.RegisterAction) {
|
||||
when (action) {
|
||||
is LoginAction.CaptchaDone -> handleCaptchaDone(action)
|
||||
is LoginAction.AcceptTerms -> handleAcceptTerms()
|
||||
is LoginAction.RegisterDummy -> handleRegisterDummy()
|
||||
is LoginAction.AddThreePid -> handleAddThreePid(action)
|
||||
is LoginAction.SendAgainThreePid -> handleSendAgainThreePid()
|
||||
is LoginAction.ValidateThreePid -> handleValidateThreePid(action)
|
||||
is LoginAction.CaptchaDone -> handleCaptchaDone(action)
|
||||
is LoginAction.AcceptTerms -> handleAcceptTerms()
|
||||
is LoginAction.RegisterDummy -> handleRegisterDummy()
|
||||
is LoginAction.AddThreePid -> handleAddThreePid(action)
|
||||
is LoginAction.SendAgainThreePid -> handleSendAgainThreePid()
|
||||
is LoginAction.ValidateThreePid -> handleValidateThreePid(action)
|
||||
is LoginAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action)
|
||||
is LoginAction.StopEmailValidationCheck -> handleStopEmailValidationCheck()
|
||||
is LoginAction.StopEmailValidationCheck -> handleStopEmailValidationCheck()
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +258,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
when (data) {
|
||||
is RegistrationResult.Success -> onSessionCreated(data.session)
|
||||
is RegistrationResult.Success -> onSessionCreated(data.session)
|
||||
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
|
||||
}
|
||||
}
|
||||
@ -337,7 +358,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
LoginAction.ResetHomeServerUrl -> {
|
||||
LoginAction.ResetHomeServerUrl -> {
|
||||
authenticationService.reset()
|
||||
|
||||
setState {
|
||||
@ -350,7 +371,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
LoginAction.ResetSignMode -> {
|
||||
LoginAction.ResetSignMode -> {
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
@ -360,7 +381,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
LoginAction.ResetLogin -> {
|
||||
LoginAction.ResetLogin -> {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
|
||||
setState {
|
||||
@ -370,7 +391,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
LoginAction.ResetResetPassword -> {
|
||||
LoginAction.ResetResetPassword -> {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Uninitialized,
|
||||
@ -390,10 +411,10 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
when (action.signMode) {
|
||||
SignMode.SignUp -> startRegistrationFlow()
|
||||
SignMode.SignIn -> startAuthenticationFlow()
|
||||
SignMode.SignUp -> startRegistrationFlow()
|
||||
SignMode.SignIn -> startAuthenticationFlow()
|
||||
SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId))
|
||||
SignMode.Unknown -> Unit
|
||||
SignMode.Unknown -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@ -405,12 +426,12 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
when (action.serverType) {
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
ServerType.MatrixOrg ->
|
||||
// Request login flow here
|
||||
handle(LoginAction.UpdateHomeServer(matrixOrgUrl))
|
||||
ServerType.EMS,
|
||||
ServerType.Other -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType))
|
||||
ServerType.Other -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType))
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@ -514,9 +535,9 @@ class LoginViewModel @AssistedInject constructor(
|
||||
|
||||
private fun handleLoginOrRegister(action: LoginAction.LoginOrRegister) = withState { state ->
|
||||
when (state.signMode) {
|
||||
SignMode.Unknown -> error("Developer error, invalid sign mode")
|
||||
SignMode.SignIn -> handleLogin(action)
|
||||
SignMode.SignUp -> handleRegisterWith(action)
|
||||
SignMode.Unknown -> error("Developer error, invalid sign mode")
|
||||
SignMode.SignIn -> handleLogin(action)
|
||||
SignMode.SignUp -> handleRegisterWith(action)
|
||||
SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
|
||||
}.exhaustive
|
||||
}
|
||||
@ -713,11 +734,11 @@ class LoginViewModel @AssistedInject constructor(
|
||||
|
||||
private fun handleUpdateHomeserver(action: LoginAction.UpdateHomeServer) {
|
||||
val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl)
|
||||
|
||||
if (homeServerConnectionConfig == null) {
|
||||
// This is invalid
|
||||
_viewEvents.post(LoginViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||
} else {
|
||||
rememberHomeServer(action.homeServerUrl)
|
||||
getLoginFlow(homeServerConnectionConfig)
|
||||
}
|
||||
}
|
||||
@ -755,9 +776,11 @@ class LoginViewModel @AssistedInject constructor(
|
||||
is LoginFlowResult.Success -> {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
||||
// FIXME We should post a view event here normally?
|
||||
|
@ -51,7 +51,8 @@ data class LoginViewState(
|
||||
val loginMode: LoginMode = LoginMode.Unknown,
|
||||
@PersistState
|
||||
// Supported types for the login. We cannot use a sealed class for LoginType because it is not serializable
|
||||
val loginModeSupportedTypes: List<String> = emptyList()
|
||||
val loginModeSupportedTypes: List<String> = emptyList(),
|
||||
val knownCustomHomeServersUrls: List<String> = emptyList()
|
||||
) : MvRxState {
|
||||
|
||||
fun isLoading(): Boolean {
|
||||
@ -68,10 +69,13 @@ data class LoginViewState(
|
||||
return asyncLoginAction is Success
|
||||
}
|
||||
|
||||
fun getSsoUrl(): String {
|
||||
fun getSsoUrl(providerId: String?): String {
|
||||
return buildString {
|
||||
append(homeServerUrl?.trim { it == '/' })
|
||||
append(SSO_REDIRECT_PATH)
|
||||
if (providerId != null) {
|
||||
append("/$providerId")
|
||||
}
|
||||
// Set a redirect url we will intercept later
|
||||
appendParamToUrl(SSO_REDIRECT_URL_PARAM, VECTOR_REDIRECT_URL)
|
||||
deviceId?.takeIf { it.isNotBlank() }?.let {
|
||||
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.login
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.children
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import im.vector.app.R
|
||||
import org.matrix.android.sdk.api.auth.data.IdentityProvider
|
||||
|
||||
class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyle) {
|
||||
|
||||
interface InteractionListener {
|
||||
fun onProviderSelected(id: IdentityProvider)
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
MODE_SIGN_IN,
|
||||
MODE_SIGN_UP,
|
||||
MODE_CONTINUE,
|
||||
}
|
||||
|
||||
var identityProviders: List<IdentityProvider>? = null
|
||||
set(newProviders) {
|
||||
if (newProviders != identityProviders) {
|
||||
field = newProviders
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
var mode: Mode = Mode.MODE_CONTINUE
|
||||
set(value) {
|
||||
if (value != mode) {
|
||||
field = value
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
var listener: InteractionListener? = null
|
||||
|
||||
private fun update() {
|
||||
val cachedViews = emptyMap<String, MaterialButton>().toMutableMap()
|
||||
children.filterIsInstance<MaterialButton>().forEach {
|
||||
cachedViews[it.getTag(R.id.loginSignupSigninSocialLoginButtons)?.toString() ?: ""] = it
|
||||
}
|
||||
removeAllViews()
|
||||
if (identityProviders.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
identityProviders?.forEach { identityProvider ->
|
||||
// Use some heuristic to render buttons according to branding guidelines
|
||||
val cached = cachedViews[identityProvider.id]
|
||||
val button: MaterialButton = if (cached != null) {
|
||||
cached
|
||||
} else {
|
||||
when (identityProvider.id) {
|
||||
"google" -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_google_style)
|
||||
}
|
||||
"github" -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_github_style)
|
||||
}
|
||||
"apple" -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_apple_style)
|
||||
}
|
||||
"facebook" -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_facebook_style)
|
||||
}
|
||||
"twitter" -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_twitter_style)
|
||||
}
|
||||
else -> {
|
||||
MaterialButton(context, null, R.attr.materialButtonStyle).apply {
|
||||
transformationMethod = null
|
||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
button.text = getButtonTitle(identityProvider)
|
||||
button.setTag(R.id.loginSignupSigninSocialLoginButtons, identityProvider.id)
|
||||
button.setOnClickListener {
|
||||
listener?.onProviderSelected(identityProvider)
|
||||
}
|
||||
addView(button)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getButtonTitle(provider: IdentityProvider): String {
|
||||
return when (mode) {
|
||||
Mode.MODE_SIGN_IN -> context.getString(R.string.login_social_signin_with, provider.name)
|
||||
Mode.MODE_SIGN_UP -> context.getString(R.string.login_social_signup_with, provider.name)
|
||||
Mode.MODE_CONTINUE -> context.getString(R.string.login_social_continue_with, provider.name)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
this.orientation = VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
clipToPadding = false
|
||||
clipChildren = false
|
||||
@SuppressLint("SetTextI18n")
|
||||
if (isInEditMode) {
|
||||
identityProviders = listOf(
|
||||
IdentityProvider("google", "Google", null),
|
||||
IdentityProvider("facebook", "Facebook", null),
|
||||
IdentityProvider("apple", "Apple", null),
|
||||
IdentityProvider("github", "Github", null),
|
||||
IdentityProvider("twitter", "Twitter", null),
|
||||
IdentityProvider("Custom_pro", "SSO", null)
|
||||
)
|
||||
}
|
||||
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.SocialLoginButtonsView, 0, 0)
|
||||
val modeAttr = typedArray.getInt(R.styleable.SocialLoginButtonsView_signMode, 2)
|
||||
mode = when (modeAttr) {
|
||||
0 -> Mode.MODE_SIGN_IN
|
||||
1 -> Mode.MODE_SIGN_UP
|
||||
else -> Mode.MODE_CONTINUE
|
||||
}
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
fun dpToPx(dp: Int): Int {
|
||||
val resources = context.resources
|
||||
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), resources.displayMetrics).toInt()
|
||||
}
|
||||
}
|
@ -107,7 +107,7 @@ class SoftLogoutController @Inject constructor(
|
||||
}
|
||||
is Success -> {
|
||||
when (state.asyncHomeServerLoginFlowRequest.invoke()) {
|
||||
LoginMode.Password -> {
|
||||
LoginMode.Password -> {
|
||||
loginPasswordFormItem {
|
||||
id("passwordForm")
|
||||
stringProvider(stringProvider)
|
||||
@ -120,21 +120,23 @@ class SoftLogoutController @Inject constructor(
|
||||
submitClickListener { password -> listener?.signinSubmit(password) }
|
||||
}
|
||||
}
|
||||
LoginMode.Sso -> {
|
||||
is LoginMode.Sso -> {
|
||||
loginCenterButtonItem {
|
||||
id("sso")
|
||||
text(stringProvider.getString(R.string.login_signin_sso))
|
||||
listener { listener?.signinFallbackSubmit() }
|
||||
}
|
||||
}
|
||||
LoginMode.Unsupported -> {
|
||||
is LoginMode.SsoAndPassword -> {
|
||||
}
|
||||
LoginMode.Unsupported -> {
|
||||
loginCenterButtonItem {
|
||||
id("fallback")
|
||||
text(stringProvider.getString(R.string.login_signin))
|
||||
listener { listener?.signinFallbackSubmit() }
|
||||
}
|
||||
}
|
||||
LoginMode.Unknown -> Unit // Should not happen
|
||||
LoginMode.Unknown -> Unit // Should not happen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,14 +54,27 @@ class SoftLogoutFragment @Inject constructor(
|
||||
|
||||
softLogoutViewModel.subscribe(this) { softLogoutViewState ->
|
||||
softLogoutController.update(softLogoutViewState)
|
||||
|
||||
when (softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) {
|
||||
LoginMode.Sso,
|
||||
when (val mode = softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) {
|
||||
is LoginMode.SsoAndPassword -> {
|
||||
loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery(
|
||||
softLogoutViewState.homeServerUrl,
|
||||
softLogoutViewState.deviceId,
|
||||
mode.identityProviders
|
||||
))
|
||||
}
|
||||
is LoginMode.Sso -> {
|
||||
loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery(
|
||||
softLogoutViewState.homeServerUrl,
|
||||
softLogoutViewState.deviceId,
|
||||
mode.identityProviders
|
||||
))
|
||||
}
|
||||
LoginMode.Unsupported -> {
|
||||
// Prepare the loginViewModel for a SSO/login fallback recovery
|
||||
loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery(
|
||||
softLogoutViewState.homeServerUrl,
|
||||
softLogoutViewState.deviceId
|
||||
softLogoutViewState.deviceId,
|
||||
null
|
||||
))
|
||||
}
|
||||
else -> Unit
|
||||
|
@ -105,9 +105,11 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
||||
is LoginFlowResult.Success -> {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
||||
setState {
|
||||
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="#4285F4" android:state_enabled="true"/>
|
||||
<item android:color="@color/riotx_disabled_view_color_light" android:state_enabled="false"/>
|
||||
<item android:color="#3367D6" android:state_pressed="true"/>
|
||||
<item android:color="#4285F4" android:state_focused="true"/>
|
||||
</selector>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/white" android:state_enabled="true"/>
|
||||
<item android:color="@color/riotx_disabled_view_color_light" android:state_enabled="false"/>
|
||||
<item android:color="@color/riotx_disabled_view_color_light" android:state_pressed="true"/>
|
||||
</selector>
|
12
vector/src/main/res/drawable/ic_social_apple.xml
Normal file
12
vector/src/main/res/drawable/ic_social_apple.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="38dp"
|
||||
android:height="38dp"
|
||||
android:viewportWidth="38"
|
||||
android:viewportHeight="38">
|
||||
<path
|
||||
android:pathData="M19.934,15.416C20.174,15.416 20.6,15.314 21.212,15.11C21.824,14.906 22.346,14.804 22.778,14.804C23.498,14.804 24.14,14.996 24.704,15.38C24.86,15.5 25.016,15.632 25.172,15.776C25.328,15.92 25.484,16.088 25.64,16.28C25.172,16.688 24.83,17.048 24.614,17.36C24.218,17.912 24.02,18.53 24.02,19.214C24.02,19.958 24.23,20.63 24.65,21.23C25.058,21.818 25.532,22.196 26.072,22.364C25.952,22.724 25.802,23.093 25.622,23.471C25.442,23.849 25.226,24.23 24.974,24.614C24.206,25.778 23.432,26.36 22.652,26.36C22.328,26.36 21.902,26.264 21.374,26.072C20.858,25.892 20.408,25.802 20.024,25.802C19.628,25.802 19.196,25.898 18.728,26.09C18.224,26.294 17.828,26.396 17.54,26.396C16.628,26.396 15.728,25.622 14.84,24.074C13.952,22.538 13.508,21.026 13.508,19.538C13.508,18.17 13.844,17.048 14.516,16.172C15.2,15.308 16.058,14.876 17.09,14.876C17.534,14.876 18.068,14.966 18.692,15.146C19.328,15.326 19.742,15.416 19.934,15.416ZM22.688,11.78C22.688,12.164 22.598,12.578 22.418,13.022C22.238,13.466 21.962,13.88 21.59,14.264C21.242,14.588 20.912,14.804 20.6,14.912C20.372,14.972 20.066,15.02 19.682,15.056C19.694,14.168 19.925,13.397 20.375,12.743C20.825,12.089 21.578,11.642 22.634,11.402C22.658,11.486 22.673,11.558 22.679,11.618C22.685,11.678 22.688,11.732 22.688,11.78Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
12
vector/src/main/res/drawable/ic_social_facebook.xml
Normal file
12
vector/src/main/res/drawable/ic_social_facebook.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="38dp"
|
||||
android:height="38dp"
|
||||
android:viewportWidth="38"
|
||||
android:viewportHeight="38">
|
||||
<path
|
||||
android:pathData="M28,19.055C28,14.0541 23.9706,10 19,10C14.0294,10 10,14.0541 10,19.055C10,23.5746 13.2912,27.3207 17.5938,28L17.5938,21.6725L15.3086,21.6725L15.3086,19.055L17.5938,19.055L17.5938,17.0601C17.5938,14.7907 18.9374,13.5371 20.9932,13.5371C21.9779,13.5371 23.0078,13.714 23.0078,13.714L23.0078,15.9423L21.8729,15.9423C20.7549,15.9423 20.4063,16.6403 20.4063,17.3564L20.4063,19.055L22.9023,19.055L22.5033,21.6725L20.4063,21.6725L20.4063,28C24.7088,27.3207 28,23.5746 28,19.055"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#FFFFFE"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
12
vector/src/main/res/drawable/ic_social_github.xml
Normal file
12
vector/src/main/res/drawable/ic_social_github.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="38dp"
|
||||
android:height="38dp"
|
||||
android:viewportWidth="38"
|
||||
android:viewportHeight="38">
|
||||
<path
|
||||
android:pathData="M18.9992,10C14.03,10 10,14.0294 10,19.0003C10,22.9766 12.5785,26.3497 16.1549,27.5398C16.6052,27.6226 16.7693,27.3447 16.7693,27.1061C16.7693,26.8928 16.7615,26.3265 16.7571,25.5756C14.2537,26.1193 13.7255,24.3689 13.7255,24.3689C13.3161,23.3291 12.7261,23.0523 12.7261,23.0523C11.9089,22.4943 12.7879,22.5054 12.7879,22.5054C13.6913,22.5689 14.1664,23.433 14.1664,23.433C14.9692,24.8082 16.2731,24.4109 16.7858,24.1805C16.8676,23.5993 17.1002,23.2026 17.3571,22.9777C15.3587,22.7507 13.2576,21.9783 13.2576,18.5295C13.2576,17.5472 13.6084,16.7433 14.1841,16.1146C14.0913,15.8869 13.7824,14.9714 14.2725,13.7327C14.2725,13.7327 15.0278,13.4907 16.7472,14.6554C17.4649,14.4554 18.2351,14.3559 19.0003,14.3521C19.7649,14.3559 20.5346,14.4554 21.2534,14.6554C22.9717,13.4907 23.7258,13.7327 23.7258,13.7327C24.217,14.9714 23.9082,15.8869 23.8159,16.1146C24.3927,16.7433 24.7408,17.5472 24.7408,18.5295C24.7408,21.9871 22.6363,22.7479 20.6318,22.9706C20.9545,23.2485 21.2423,23.7977 21.2423,24.6375C21.2423,25.8403 21.2313,26.811 21.2313,27.1061C21.2313,27.3469 21.3937,27.6271 21.8501,27.5392C25.4237,26.3464 28,22.9755 28,19.0003C28,14.0294 23.97,10 18.9992,10"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#161514"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
36
vector/src/main/res/drawable/ic_social_google.xml
Normal file
36
vector/src/main/res/drawable/ic_social_google.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="38dp"
|
||||
android:height="38dp"
|
||||
android:viewportWidth="38"
|
||||
android:viewportHeight="38">
|
||||
<path
|
||||
android:pathData="M1,0L37,0A1,1 0,0 1,38 1L38,37A1,1 0,0 1,37 38L1,38A1,1 0,0 1,0 37L0,1A1,1 0,0 1,1 0z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M27.64,19.2045C27.64,18.5664 27.5827,17.9527 27.4764,17.3636L19,17.3636L19,20.845L23.8436,20.845C23.635,21.97 23.0009,22.9232 22.0477,23.5614L22.0477,25.8195L24.9564,25.8195C26.6582,24.2527 27.64,21.9455 27.64,19.2045L27.64,19.2045Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#4285F4"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M19,28C21.43,28 23.4673,27.1941 24.9564,25.8195L22.0477,23.5614C21.2418,24.1014 20.2109,24.4205 19,24.4205C16.6559,24.4205 14.6718,22.8373 13.9641,20.71L10.9573,20.71L10.9573,23.0418C12.4382,25.9832 15.4818,28 19,28L19,28Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#34A853"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M13.9641,20.71C13.7841,20.17 13.6818,19.5932 13.6818,19C13.6818,18.4068 13.7841,17.83 13.9641,17.29L13.9641,14.9582L10.9573,14.9582C10.3477,16.1732 10,17.5477 10,19C10,20.4523 10.3477,21.8268 10.9573,23.0418L13.9641,20.71L13.9641,20.71Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#FBBC05"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M19,13.5795C20.3214,13.5795 21.5077,14.0336 22.4405,14.9255L25.0218,12.3441C23.4632,10.8918 21.4259,10 19,10C15.4818,10 12.4382,12.0168 10.9573,14.9582L13.9641,17.29C14.6718,15.1627 16.6559,13.5795 19,13.5795L19,13.5795Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#EA4335"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
12
vector/src/main/res/drawable/ic_social_twitter.xml
Normal file
12
vector/src/main/res/drawable/ic_social_twitter.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="38dp"
|
||||
android:height="38dp"
|
||||
android:viewportWidth="38"
|
||||
android:viewportHeight="38">
|
||||
<path
|
||||
android:pathData="M28,13.7317C27.3377,14.0254 26.626,14.2239 25.879,14.3132C26.6415,13.8561 27.227,13.1324 27.5027,12.2701C26.7892,12.6932 25.9989,13.0006 25.1577,13.1662C24.484,12.4485 23.5243,12 22.4621,12C20.4226,12 18.7691,13.6534 18.7691,15.6928C18.7691,15.9823 18.8018,16.2641 18.8648,16.5344C15.7956,16.3804 13.0745,14.9102 11.2531,12.676C10.9352,13.2214 10.7531,13.8558 10.7531,14.5325C10.7531,15.8137 11.4051,16.9441 12.396,17.6063C11.7906,17.5871 11.2212,17.421 10.7233,17.1444C10.723,17.1598 10.723,17.1753 10.723,17.1908C10.723,18.9801 11.9959,20.4727 13.6853,20.8119C13.3754,20.8963 13.0492,20.9414 12.7123,20.9414C12.4744,20.9414 12.243,20.9183 12.0176,20.8752C12.4875,22.3423 13.8513,23.41 15.4673,23.4398C14.2034,24.4303 12.6111,25.0207 10.8809,25.0207C10.5829,25.0207 10.2889,25.0032 10,24.9691C11.6343,26.0169 13.5754,26.6282 15.6609,26.6282C22.4535,26.6282 26.1679,21.0011 26.1679,16.1211C26.1679,15.9609 26.1644,15.8017 26.1573,15.6433C26.8787,15.1227 27.5049,14.4722 28,13.7317"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
@ -19,9 +19,9 @@
|
||||
android:id="@+id/loginServerIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:src="@drawable/ic_logo_matrix_org"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix" />
|
||||
tools:ignore="MissingPrefix"
|
||||
tools:src="@drawable/ic_logo_matrix_org" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginTitle"
|
||||
@ -95,8 +95,8 @@
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_eye"
|
||||
tools:contentDescription="@string/a11y_show_password"
|
||||
app:tint="?attr/colorAccent"
|
||||
tools:contentDescription="@string/a11y_show_password"
|
||||
tools:ignore="MissingPrefix" />
|
||||
|
||||
</FrameLayout>
|
||||
@ -136,6 +136,37 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- Social Logins buttons -->
|
||||
<LinearLayout
|
||||
android:id="@+id/loginSocialLoginContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginSocialLoginHeader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/login_social_continue"
|
||||
android:textAppearance="@style/TextAppearance.Vector.Login.Text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<im.vector.app.features.login.SocialLoginButtonsView
|
||||
app:signMode="signin"
|
||||
android:id="@+id/loginSocialLoginButtons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
@ -53,14 +53,14 @@
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/loginServerUrlFormHomeServerUrlTil"
|
||||
style="@style/VectorTextInputLayout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="26dp"
|
||||
app:errorEnabled="true"
|
||||
tools:hint="@string/login_server_url_form_modular_hint">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/loginServerUrlFormHomeServerUrl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -75,12 +75,45 @@
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/login_signin"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/loginSignupSigninSignInSocialLoginContainer"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginSignupSigninSubmit"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Social Logins buttons -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/loginSignupSigninSignInSocialLoginContainer"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginSignupSigninSignIn"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="0dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:gravity="center"
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false"
|
||||
android:padding="8dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginSignupSigninSocialLoginHeader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="27dp"
|
||||
android:gravity="center"
|
||||
android:textSize="14sp"
|
||||
android:textAppearance="@style/TextAppearance.Vector.Login.Text"
|
||||
android:text="@string/login_social_continue" />
|
||||
|
||||
<im.vector.app.features.login.SocialLoginButtonsView
|
||||
app:signMode="continue_with"
|
||||
android:id="@+id/loginSignupSigninSocialLoginButtons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
@ -41,6 +41,12 @@
|
||||
<attr name="vctr_icon_tint_on_light_action_bar_color" format="color" />
|
||||
<attr name="vctr_settings_icon_tint_color" format="color" />
|
||||
|
||||
<attr name="vctr_social_login_button_google_style" format="reference" />
|
||||
<attr name="vctr_social_login_button_github_style" format="reference" />
|
||||
<attr name="vctr_social_login_button_facebook_style" format="reference" />
|
||||
<attr name="vctr_social_login_button_twitter_style" format="reference" />
|
||||
<attr name="vctr_social_login_button_apple_style" format="reference" />
|
||||
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="PollResultLineView">
|
||||
@ -66,4 +72,12 @@
|
||||
<attr name="leftIcon" />
|
||||
<attr name="textColor" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SocialLoginButtonsView">
|
||||
<attr name="signMode" format="enum">
|
||||
<enum name="signin" value="0"/>
|
||||
<enum name="signup" value="1"/>
|
||||
<enum name="continue_with" value="2"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
@ -41,6 +41,7 @@
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="black_alpha">#55000000</color>
|
||||
<color name="black_54">#8A000000</color>
|
||||
|
||||
<!-- Palette: format fo naming:
|
||||
'riotx_<name in the palette snake case>_<theme>'
|
||||
|
@ -1984,6 +1984,12 @@
|
||||
<string name="login_server_other_title">Other</string>
|
||||
<string name="login_server_other_text">Custom & advanced settings</string>
|
||||
|
||||
|
||||
<string name="login_social_continue">Or</string>
|
||||
<string name="login_social_continue_with">Continue with %s</string>
|
||||
<string name="login_social_signup_with">Sign up with %s</string>
|
||||
<string name="login_social_signin_with">Sign in with %s</string>
|
||||
|
||||
<string name="login_continue">Continue</string>
|
||||
<!-- Replaced string is the homeserver url -->
|
||||
<string name="login_connect_to">Connect to %1$s</string>
|
||||
|
@ -370,5 +370,103 @@
|
||||
<item name="android:background">@drawable/vector_tabbar_background</item>
|
||||
<item name="background">@drawable/vector_tabbar_background</item>
|
||||
</style>
|
||||
<style name="WidgetButtonSocialLogin" parent="Widget.MaterialComponents.Button">
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="fontFamily">sans-serif-medium</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="iconGravity">start</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textAlignment">textStart</item>
|
||||
<item name="android:paddingStart">2dp</item>
|
||||
<item name="android:paddingEnd">8dp</item>
|
||||
<item name="android:clipToPadding">false</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Google">
|
||||
<item name="icon">@drawable/ic_social_google</item>
|
||||
<item name="iconTint">@android:color/transparent</item>
|
||||
<item name="iconTintMode">add</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Google.Light">
|
||||
<item name="android:backgroundTint">@color/button_social_google_background_selector_light</item>
|
||||
<item name="android:textColor">@color/black_54</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Google.Dark" >
|
||||
<item name="android:backgroundTint">@color/button_social_google_background_selector_dark</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Github" parent="WidgetButtonSocialLogin">
|
||||
<item name="icon">@drawable/ic_social_github</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Github.Light">
|
||||
<item name="iconTint">@android:color/black</item>
|
||||
<item name="android:textColor">@color/black</item>
|
||||
<item name="android:backgroundTint">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Github.Dark" >
|
||||
<item name="iconTint">@android:color/white</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:backgroundTint">@color/black</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Facebook" parent="WidgetButtonSocialLogin">
|
||||
<item name="icon">@drawable/ic_social_facebook</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Facebook.Light">
|
||||
<item name="strokeColor">#3877EA</item>
|
||||
<item name="strokeWidth">1dp</item>
|
||||
<item name="iconTint">#3877EA</item>
|
||||
<item name="android:textColor">#3877EA</item>
|
||||
<item name="android:backgroundTint">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Facebook.Dark" >
|
||||
<item name="iconTint">@android:color/white</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:backgroundTint">#3877EA</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Twitter" parent="WidgetButtonSocialLogin">
|
||||
<item name="icon">@drawable/ic_social_twitter</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Twitter.Light" >
|
||||
<item name="iconTint">#5D9EC9</item>
|
||||
<item name="android:textColor">#5D9EC9</item>
|
||||
<item name="android:backgroundTint">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Twitter.Dark">
|
||||
<item name="iconTint">@color/white</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:backgroundTint">#5D9EC9</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Apple" parent="WidgetButtonSocialLogin">
|
||||
<item name="icon">@drawable/ic_social_apple</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Apple.Light" >
|
||||
<item name="iconTint">@color/white</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:backgroundTint">@color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetButtonSocialLogin.Apple.Dark">
|
||||
<item name="iconTint">@color/black</item>
|
||||
<item name="android:textColor">@color/black</item>
|
||||
<item name="android:backgroundTint">@color/white</item>
|
||||
</style>
|
||||
|
||||
|
||||
</resources>
|
@ -194,6 +194,12 @@
|
||||
<!-- specify shared element enter and exit transitions -->
|
||||
<item name="android:windowSharedElementEnterTransition">@transition/image_preview_transition</item>
|
||||
<item name="android:windowSharedElementExitTransition">@transition/image_preview_transition</item>
|
||||
|
||||
<item name="vctr_social_login_button_google_style">@style/WidgetButtonSocialLogin.Google.Dark</item>
|
||||
<item name="vctr_social_login_button_github_style">@style/WidgetButtonSocialLogin.Github.Dark</item>
|
||||
<item name="vctr_social_login_button_facebook_style">@style/WidgetButtonSocialLogin.Facebook.Dark</item>
|
||||
<item name="vctr_social_login_button_twitter_style">@style/WidgetButtonSocialLogin.Twitter.Dark</item>
|
||||
<item name="vctr_social_login_button_apple_style">@style/WidgetButtonSocialLogin.Apple.Dark</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dark" parent="AppTheme.Base.Dark" />
|
||||
|
@ -196,6 +196,13 @@
|
||||
<!-- specify shared element enter and exit transitions -->
|
||||
<item name="android:windowSharedElementEnterTransition">@transition/image_preview_transition</item>
|
||||
<item name="android:windowSharedElementExitTransition">@transition/image_preview_transition</item>
|
||||
|
||||
|
||||
<item name="vctr_social_login_button_google_style">@style/WidgetButtonSocialLogin.Google.Light</item>
|
||||
<item name="vctr_social_login_button_github_style">@style/WidgetButtonSocialLogin.Github.Light</item>
|
||||
<item name="vctr_social_login_button_facebook_style">@style/WidgetButtonSocialLogin.Facebook.Light</item>
|
||||
<item name="vctr_social_login_button_twitter_style">@style/WidgetButtonSocialLogin.Twitter.Light</item>
|
||||
<item name="vctr_social_login_button_apple_style">@style/WidgetButtonSocialLogin.Apple.Light</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Light" parent="AppTheme.Base.Light" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user