Merge pull request #2484 from vector-im/feature/bca/social_login
Social Login
This commit is contained in:
commit
a027ef29e5
|
@ -7,11 +7,13 @@ Features ✨:
|
|||
- Url preview (#481)
|
||||
- Store encrypted file in cache and cleanup decrypted file at each app start (#2512)
|
||||
- Emoji Keyboard (#2520)
|
||||
- Social login (#2452)
|
||||
|
||||
Improvements 🙌:
|
||||
- Add Setting Item to Change PIN (#2462)
|
||||
- Improve room history visibility setting UX (#1579)
|
||||
- Matrix.to deeplink custom scheme support
|
||||
- Homeserver history (#1933)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix cancellation of sending event (#2438)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.auth
|
|||
/**
|
||||
* Path to use when the client does not supported any or all login flows
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#login-fallback
|
||||
* */
|
||||
*/
|
||||
const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.api.auth
|
||||
|
||||
/**
|
||||
* A simple service to remember homeservers you already connected to.
|
||||
*/
|
||||
interface HomeServerHistoryService {
|
||||
|
||||
fun getKnownServersUrls(): List<String>
|
||||
|
||||
fun addHomeServerToHistory(url: String)
|
||||
|
||||
fun clearHistory()
|
||||
}
|
|
@ -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<SsoIdentityProvider>?,
|
||||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String,
|
||||
val isOutdatedHomeserver: Boolean
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.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 SsoIdentityProvider(
|
||||
/**
|
||||
* 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 iconUrl: String?
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
// Not really defined by the spec, but we may define some ids here
|
||||
const val ID_GOOGLE = "google"
|
||||
const val ID_GITHUB = "github"
|
||||
const val ID_APPLE = "apple"
|
||||
const val ID_FACEBOOK = "facebook"
|
||||
const val ID_TWITTER = "twitter"
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ sealed class CallState {
|
|||
* Connected. Incoming/Outgoing call, ice layer connecting or connected
|
||||
* Notice that the PeerState failed is not always final, if you switch network, new ice candidtates
|
||||
* could be exchanged, and the connection could go back to connected
|
||||
* */
|
||||
*/
|
||||
data class Connected(val iceConnectionState: PeerConnection.PeerConnectionState) : CallState()
|
||||
|
||||
/** Terminated. Incoming/Outgoing call, the call is terminated */
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.di.AuthDatabase
|
|||
import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter
|
||||
import org.matrix.android.sdk.internal.wellknown.WellknownModule
|
||||
import io.realm.RealmConfiguration
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import java.io.File
|
||||
|
||||
@Module(includes = [WellknownModule::class])
|
||||
|
@ -80,4 +81,7 @@ internal abstract class AuthModule {
|
|||
|
||||
@Binds
|
||||
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService
|
||||
}
|
||||
|
|
|
@ -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 }?.ssoIdentityProvider,
|
||||
versions.isLoginAndRegistrationSupportedBySdk(),
|
||||
homeServerUrl,
|
||||
!versions.isSupportedBySdk()
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.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.SsoIdentityProvider
|
||||
|
||||
@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 ssoIdentityProvider: List<SsoIdentityProvider>?
|
||||
)
|
||||
|
|
|
@ -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,41 @@
|
|||
/*
|
||||
* 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.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)
|
||||
.addPrimaryKey(KnownServerUrlEntityFields.URL)
|
||||
.setRequired(KnownServerUrlEntityFields.URL, true)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -57,6 +57,9 @@ internal abstract class RawModule {
|
|||
realmKeysUtils.configureEncryption(this, DB_ALIAS)
|
||||
}
|
||||
.name("matrix-sdk-global.realm")
|
||||
.schemaVersion(GlobalRealmMigration.SCHEMA_VERSION)
|
||||
.migration(GlobalRealmMigration)
|
||||
.allowWritesOnUiThread(true)
|
||||
.modules(GlobalRealmModule())
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -230,11 +229,6 @@ interface FragmentModule {
|
|||
@FragmentKey(LoginSignUpSignInSelectionFragment::class)
|
||||
fun bindLoginSignUpSignInSelectionFragment(fragment: LoginSignUpSignInSelectionFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(LoginSignUpSignInSsoFragment::class)
|
||||
fun bindLoginSignUpSignInSsoFragment(fragment: LoginSignUpSignInSsoFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(LoginSplashFragment::class)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 com.airbnb.mvrx.withState
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
|
||||
abstract class AbstractSSOLoginFragment : AbstractLoginFragment() {
|
||||
|
||||
// For sso
|
||||
private var customTabsServiceConnection: CustomTabsServiceConnection? = null
|
||||
private var customTabsClient: CustomTabsClient? = null
|
||||
private var customTabsSession: CustomTabsSession? = null
|
||||
|
||||
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) }
|
||||
prefetchIfNeeded()
|
||||
}
|
||||
|
||||
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 prefetchUrl(url: String) {
|
||||
if (customTabsSession == null) {
|
||||
customTabsSession = customTabsClient?.newSession(null)
|
||||
}
|
||||
|
||||
customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null)
|
||||
}
|
||||
|
||||
fun openInCustomTab(ssoUrl: String) {
|
||||
openUrlInChromeCustomTab(requireContext(), customTabsSession, ssoUrl)
|
||||
}
|
||||
|
||||
private fun prefetchIfNeeded() {
|
||||
withState(loginViewModel) { state ->
|
||||
if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) {
|
||||
// in this case we can prefetch (not other cases for privacy concerns)
|
||||
prefetchUrl(state.getSsoUrl(null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.SsoIdentityProvider
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.internal.network.ssl.Fingerprint
|
||||
|
||||
|
@ -59,8 +60,13 @@ sealed class LoginAction : VectorViewModelAction {
|
|||
object ResetLogin : ResetAction()
|
||||
object ResetResetPassword : ResetAction()
|
||||
|
||||
// Homeserver history
|
||||
object ClearHomeServerHistory : LoginAction()
|
||||
|
||||
// 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 ssoIdentityProviders: List<SsoIdentityProvider>?) : LoginAction()
|
||||
|
||||
data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction()
|
||||
|
||||
|
|
|
@ -157,11 +157,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
|||
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 ->
|
||||
|
@ -252,7 +248,8 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
|||
// 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,
|
||||
|
|
|
@ -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
|
||||
|
@ -50,7 +51,7 @@ import javax.inject.Inject
|
|||
* In signup mode:
|
||||
* - the user is asked for login and password
|
||||
*/
|
||||
class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
class LoginFragment @Inject constructor() : AbstractSSOLoginFragment() {
|
||||
|
||||
private var passwordShown = false
|
||||
private var isSignupMode = false
|
||||
|
@ -83,11 +84,13 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
|||
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
|
||||
}
|
||||
|
@ -169,6 +172,19 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
|||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
}
|
||||
loginPasswordNotice.isVisible = false
|
||||
|
||||
if (state.loginMode is LoginMode.SsoAndPassword) {
|
||||
loginSocialLoginContainer.isVisible = true
|
||||
loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders
|
||||
loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||
override fun onProviderSelected(id: String?) {
|
||||
openInCustomTab(state.getSsoUrl(id))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loginSocialLoginContainer.isVisible = false
|
||||
loginSocialLoginButtons.ssoIdentityProviders = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.SsoIdentityProvider
|
||||
|
||||
sealed class LoginMode : Parcelable
|
||||
/** because persist state */ {
|
||||
@Parcelize object Unknown : LoginMode()
|
||||
@Parcelize object Password : LoginMode()
|
||||
@Parcelize data class Sso(val ssoIdentityProviders: List<SsoIdentityProvider>?) : LoginMode()
|
||||
@Parcelize data class SsoAndPassword(val ssoIdentityProviders: List<SsoIdentityProvider>?) : LoginMode()
|
||||
@Parcelize object Unsupported : LoginMode()
|
||||
}
|
||||
|
||||
fun LoginMode.ssoIdentityProviders() : List<SsoIdentityProvider>? {
|
||||
return when (this) {
|
||||
is LoginMode.Sso -> ssoIdentityProviders
|
||||
is LoginMode.SsoAndPassword -> ssoIdentityProviders
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,13 @@ 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.isInvisible
|
||||
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.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.utils.ensureProtocol
|
||||
|
@ -55,6 +59,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
|||
|
||||
loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
loginServerUrlFormHomeServerUrl.dismissDropDown()
|
||||
submit()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
|
@ -81,6 +86,15 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
|||
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice)
|
||||
}
|
||||
}
|
||||
val completions = state.knownCustomHomeServersUrls + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList()
|
||||
loginServerUrlFormHomeServerUrl.setAdapter(ArrayAdapter(
|
||||
requireContext(),
|
||||
R.layout.item_completion_homeserver,
|
||||
completions
|
||||
))
|
||||
loginServerUrlFormHomeServerUrlTil.endIconMode = TextInputLayout.END_ICON_DROPDOWN_MENU
|
||||
.takeIf { completions.isNotEmpty() }
|
||||
?: TextInputLayout.END_ICON_NONE
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginServerUrlFormLearnMore)
|
||||
|
@ -88,6 +102,11 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
|||
openUrlInChromeCustomTab(requireActivity(), null, EMS_LINK)
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginServerUrlFormClearHistory)
|
||||
fun clearHistory() {
|
||||
loginViewModel.handle(LoginAction.ClearHomeServerHistory)
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
loginViewModel.handle(LoginAction.ResetHomeServerUrl)
|
||||
}
|
||||
|
@ -105,7 +124,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))
|
||||
}
|
||||
}
|
||||
|
@ -129,9 +148,11 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
|||
override fun updateWithState(state: LoginViewState) {
|
||||
setupUi(state)
|
||||
|
||||
loginServerUrlFormClearHistory.isInvisible = state.knownCustomHomeServersUrls.isEmpty()
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.login
|
|||
|
||||
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 kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
|
||||
|
@ -26,11 +27,11 @@ 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() : AbstractSSOLoginFragment() {
|
||||
|
||||
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)
|
||||
|
@ -38,29 +39,61 @@ open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLo
|
|||
loginSignupSigninTitle.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl())
|
||||
loginSignupSigninText.text = getString(R.string.login_server_matrix_org_text)
|
||||
}
|
||||
ServerType.EMS -> {
|
||||
ServerType.EMS -> {
|
||||
loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_element_matrix_services)
|
||||
loginSignupSigninServerIcon.isVisible = true
|
||||
loginSignupSigninTitle.text = getString(R.string.login_connect_to_modular)
|
||||
loginSignupSigninText.text = state.homeServerUrl.toReducedUrl()
|
||||
}
|
||||
ServerType.Other -> {
|
||||
ServerType.Other -> {
|
||||
loginSignupSigninServerIcon.isVisible = false
|
||||
loginSignupSigninTitle.text = getString(R.string.login_server_other_title)
|
||||
loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl())
|
||||
}
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
ServerType.Unknown -> Unit /* Should not happen */
|
||||
}
|
||||
|
||||
when (state.loginMode) {
|
||||
is LoginMode.SsoAndPassword -> {
|
||||
loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
||||
loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()
|
||||
loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||
override fun onProviderSelected(id: String?) {
|
||||
val url = withState(loginViewModel) { it.getSsoUrl(id) }
|
||||
openInCustomTab(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// SSO only is managed without container as well as No sso
|
||||
loginSignupSigninSignInSocialLoginContainer.isVisible = false
|
||||
loginSignupSigninSocialLoginButtons.ssoIdentityProviders = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
loginSignupSigninSubmit.text = getString(R.string.login_signup)
|
||||
loginSignupSigninSignIn.isVisible = true
|
||||
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) {
|
||||
openInCustomTab(state.getSsoUrl(null))
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
@OnClick(R.id.loginSignupSigninSignIn)
|
||||
|
@ -74,6 +107,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())
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ sealed class LoginViewEvents : VectorViewEvents {
|
|||
|
||||
object OpenServerSelection : LoginViewEvents()
|
||||
data class OnServerSelectionDone(val serverType: ServerType) : LoginViewEvents()
|
||||
data class OnLoginFlowRetrieved(val isSso: Boolean) : LoginViewEvents()
|
||||
object OnLoginFlowRetrieved : LoginViewEvents()
|
||||
data class OnSignModeSelected(val signMode: SignMode) : LoginViewEvents()
|
||||
object OnForgetPasswordClicked : LoginViewEvents()
|
||||
object OnResetPasswordSendThreePidDone : LoginViewEvents()
|
||||
|
|
|
@ -38,6 +38,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 +64,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,6 +73,16 @@ class LoginViewModel @AssistedInject constructor(
|
|||
fun create(initialState: LoginViewState): LoginViewModel
|
||||
}
|
||||
|
||||
init {
|
||||
getKnownCustomHomeServersUrls()
|
||||
}
|
||||
|
||||
private fun getKnownCustomHomeServersUrls() {
|
||||
setState {
|
||||
copy(knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls())
|
||||
}
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<LoginViewModel, LoginViewState> {
|
||||
|
||||
@JvmStatic
|
||||
|
@ -121,6 +133,7 @@ class LoginViewModel @AssistedInject constructor(
|
|||
is LoginAction.ResetAction -> handleResetAction(action)
|
||||
is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
|
||||
is LoginAction.UserAcceptCertificate -> handleUserAcceptCertificate(action)
|
||||
LoginAction.ClearHomeServerHistory -> handleClearHomeServerHistory()
|
||||
is LoginAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
|
||||
}.exhaustive
|
||||
}
|
||||
|
@ -129,10 +142,11 @@ 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 -> {
|
||||
currentHomeServerConnectionConfig
|
||||
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
||||
?.let { getLoginFlow(it) }
|
||||
}
|
||||
is LoginAction.LoginOrRegister ->
|
||||
handleDirectLogin(
|
||||
finalLastAction,
|
||||
|
@ -145,6 +159,16 @@ class LoginViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun rememberHomeServer(homeServerUrl: String) {
|
||||
homeServerHistoryService.addHomeServerToHistory(homeServerUrl)
|
||||
getKnownCustomHomeServersUrls()
|
||||
}
|
||||
|
||||
private fun handleClearHomeServerHistory() {
|
||||
homeServerHistoryService.clearHistory()
|
||||
getKnownCustomHomeServersUrls()
|
||||
}
|
||||
|
||||
private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
|
||||
val safeLoginWizard = loginWizard
|
||||
|
||||
|
@ -184,7 +208,7 @@ class LoginViewModel @AssistedInject constructor(
|
|||
setState {
|
||||
copy(
|
||||
signMode = SignMode.SignIn,
|
||||
loginMode = LoginMode.Sso,
|
||||
loginMode = LoginMode.Sso(action.ssoIdentityProviders),
|
||||
homeServerUrl = action.homeServerUrl,
|
||||
deviceId = action.deviceId
|
||||
)
|
||||
|
@ -713,7 +737,6 @@ 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")))
|
||||
|
@ -751,13 +774,19 @@ class LoginViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun onSuccess(data: LoginFlowResult) {
|
||||
// Valid Homeserver, add it to the history.
|
||||
// Note: we add what the user has input, data.homeServerUrl can be different
|
||||
rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
|
||||
|
||||
when (data) {
|
||||
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,157 @@
|
|||
/*
|
||||
* 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.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.SsoIdentityProvider
|
||||
|
||||
class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyle) {
|
||||
|
||||
interface InteractionListener {
|
||||
fun onProviderSelected(id: String?)
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
MODE_SIGN_IN,
|
||||
MODE_SIGN_UP,
|
||||
MODE_CONTINUE,
|
||||
}
|
||||
|
||||
var ssoIdentityProviders: List<SsoIdentityProvider>? = null
|
||||
set(newProviders) {
|
||||
if (newProviders != ssoIdentityProviders) {
|
||||
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 (ssoIdentityProviders.isNullOrEmpty()) {
|
||||
// Put a default sign in with sso button
|
||||
MaterialButton(context, null, R.attr.materialButtonOutlinedStyle).apply {
|
||||
transformationMethod = null
|
||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
}.let {
|
||||
it.text = getButtonTitle(context.getString(R.string.login_social_sso))
|
||||
it.textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
it.setOnClickListener {
|
||||
listener?.onProviderSelected(null)
|
||||
}
|
||||
addView(it)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ssoIdentityProviders?.forEach { identityProvider ->
|
||||
// Use some heuristic to render buttons according to branding guidelines
|
||||
val button: MaterialButton = cachedViews[identityProvider.id]
|
||||
?: when (identityProvider.id) {
|
||||
SsoIdentityProvider.ID_GOOGLE -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_google_style)
|
||||
}
|
||||
SsoIdentityProvider.ID_GITHUB -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_github_style)
|
||||
}
|
||||
SsoIdentityProvider.ID_APPLE -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_apple_style)
|
||||
}
|
||||
SsoIdentityProvider.ID_FACEBOOK -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_facebook_style)
|
||||
}
|
||||
SsoIdentityProvider.ID_TWITTER -> {
|
||||
MaterialButton(context, null, R.attr.vctr_social_login_button_twitter_style)
|
||||
}
|
||||
else -> {
|
||||
// TODO Use iconUrl
|
||||
MaterialButton(context, null, R.attr.materialButtonStyle).apply {
|
||||
transformationMethod = null
|
||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
button.text = getButtonTitle(identityProvider.name)
|
||||
button.setTag(R.id.loginSignupSigninSocialLoginButtons, identityProvider.id)
|
||||
button.setOnClickListener {
|
||||
listener?.onProviderSelected(identityProvider.id)
|
||||
}
|
||||
addView(button)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getButtonTitle(providerName: String?): String {
|
||||
return when (mode) {
|
||||
Mode.MODE_SIGN_IN -> context.getString(R.string.login_social_signin_with, providerName)
|
||||
Mode.MODE_SIGN_UP -> context.getString(R.string.login_social_signup_with, providerName)
|
||||
Mode.MODE_CONTINUE -> context.getString(R.string.login_social_continue_with, providerName)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
this.orientation = VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
clipToPadding = false
|
||||
clipChildren = false
|
||||
if (isInEditMode) {
|
||||
ssoIdentityProviders = listOf(
|
||||
SsoIdentityProvider(SsoIdentityProvider.ID_GOOGLE, "Google", null),
|
||||
SsoIdentityProvider(SsoIdentityProvider.ID_FACEBOOK, "Facebook", null),
|
||||
SsoIdentityProvider(SsoIdentityProvider.ID_APPLE, "Apple", null),
|
||||
SsoIdentityProvider(SsoIdentityProvider.ID_GITHUB, "GitHub", null),
|
||||
SsoIdentityProvider(SsoIdentityProvider.ID_TWITTER, "Twitter", null),
|
||||
SsoIdentityProvider("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()
|
||||
update()
|
||||
}
|
||||
|
||||
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.ssoIdentityProviders
|
||||
))
|
||||
}
|
||||
is LoginMode.Sso -> {
|
||||
loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery(
|
||||
softLogoutViewState.homeServerUrl,
|
||||
softLogoutViewState.deviceId,
|
||||
mode.ssoIdentityProviders
|
||||
))
|
||||
}
|
||||
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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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,35 @@
|
|||
|
||||
</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: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
|
||||
android:id="@+id/loginSocialLoginButtons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:signMode="signin" />
|
||||
|
||||
</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"
|
||||
|
@ -70,6 +70,17 @@
|
|||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginServerUrlFormClearHistory"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/login_clear_homeserver_history"
|
||||
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
|
||||
android:textColor="@color/riotx_accent"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginServerUrlFormNotice"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -26,10 +26,10 @@
|
|||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginLogo"
|
||||
tools:src="@drawable/ic_logo_matrix_org"
|
||||
tools:visibility="visible"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix,UnknownId" />
|
||||
tools:ignore="MissingPrefix,UnknownId"
|
||||
tools:src="@drawable/ic_logo_matrix_org"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginSignupSigninTitle"
|
||||
|
@ -75,12 +75,43 @@
|
|||
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:id="@+id/loginSignupSigninSignInSocialLoginContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginSignupSigninSignIn">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginSignupSigninSocialLoginHeader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="27dp"
|
||||
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
|
||||
android:id="@+id/loginSignupSigninSocialLoginButtons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:signMode="continue_with" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textColor="?android:attr/textColorAlertDialogListItem"
|
||||
android:textSize="14sp"
|
||||
tools:text="https://matrix.org" />
|
|
@ -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,13 @@
|
|||
<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_social_sso">single sign-on</string>
|
||||
|
||||
<string name="login_continue">Continue</string>
|
||||
<!-- Replaced string is the homeserver url -->
|
||||
<string name="login_connect_to">Connect to %1$s</string>
|
||||
|
@ -1994,6 +2001,7 @@
|
|||
<string name="login_signup">Sign Up</string>
|
||||
<string name="login_signin">Sign In</string>
|
||||
<string name="login_signin_sso">Continue with SSO</string>
|
||||
<string name="login_clear_homeserver_history">Clear history</string>
|
||||
|
||||
<string name="login_server_url_form_modular_hint">Element Matrix Services Address</string>
|
||||
<string name="login_server_url_form_other_hint">Address</string>
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<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…
Reference in New Issue