mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Import and adapt Terms Of Service management: SDK and UI (compiling)
This commit is contained in:
parent
8dd5f88dba
commit
e86460b578
38 changed files with 1128 additions and 67 deletions
|
@ -40,6 +40,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
|
|||
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||
import im.vector.matrix.android.api.session.sync.FilterService
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
|
||||
/**
|
||||
|
@ -55,6 +56,7 @@ interface Session :
|
|||
SignOutService,
|
||||
FilterService,
|
||||
FileService,
|
||||
TermsService,
|
||||
ProfileService,
|
||||
PushRuleService,
|
||||
PushersService,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.matrix.android.api.session.terms
|
||||
|
||||
import im.vector.matrix.android.internal.session.terms.TermsResponse
|
||||
|
||||
data class GetTermsResponse(
|
||||
val serverResponse: TermsResponse,
|
||||
val alreadyAcceptedTermUrls: Set<String>
|
||||
)
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.matrix.android.api.session.terms
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface TermsService {
|
||||
enum class ServiceType {
|
||||
IntegrationManager,
|
||||
IdentityService
|
||||
}
|
||||
|
||||
fun getTerms(serviceType: ServiceType,
|
||||
baseUrl: String,
|
||||
callback: MatrixCallback<GetTermsResponse>): Cancelable
|
||||
|
||||
fun agreeToTerms(serviceType: ServiceType,
|
||||
baseUrl: String,
|
||||
agreedUrls: List<String>,
|
||||
token: String?,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.androidsdk.rest.model.login
|
||||
package im.vector.matrix.android.internal.auth.registration
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
|
|
@ -32,4 +32,7 @@ internal object NetworkConstants {
|
|||
const val URI_IDENTITY_PATH_V2 = "_matrix/identity/v2/"
|
||||
|
||||
const val URI_API_PREFIX_IDENTITY = "_matrix/identity/api/v1"
|
||||
|
||||
// TODO Ganfra, use correct value
|
||||
const val URI_INTEGRATION_MANAGER_PATH = "TODO/"
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
|
|||
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||
import im.vector.matrix.android.api.session.sync.FilterService
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||
|
@ -84,6 +85,7 @@ internal class DefaultSession @Inject constructor(
|
|||
private val signOutService: Lazy<SignOutService>,
|
||||
private val pushRuleService: Lazy<PushRuleService>,
|
||||
private val pushersService: Lazy<PushersService>,
|
||||
private val termsService: Lazy<TermsService>,
|
||||
private val cryptoService: Lazy<DefaultCryptoService>,
|
||||
private val fileService: Lazy<FileService>,
|
||||
private val secureStorageService: Lazy<SecureStorageService>,
|
||||
|
@ -112,6 +114,7 @@ internal class DefaultSession @Inject constructor(
|
|||
PushRuleService by pushRuleService.get(),
|
||||
PushersService by pushersService.get(),
|
||||
FileService by fileService.get(),
|
||||
TermsService by termsService.get(),
|
||||
InitialSyncProgressService by initialSyncProgressService.get(),
|
||||
SecureStorageService by secureStorageService.get(),
|
||||
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
|
||||
|
|
|
@ -52,6 +52,7 @@ import im.vector.matrix.android.internal.session.sync.SyncModule
|
|||
import im.vector.matrix.android.internal.session.sync.SyncTask
|
||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||
import im.vector.matrix.android.internal.session.terms.TermsModule
|
||||
import im.vector.matrix.android.internal.session.user.UserModule
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
|
@ -74,6 +75,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
|||
PushersModule::class,
|
||||
OpenIdModule::class,
|
||||
IdentityModule::class,
|
||||
TermsModule::class,
|
||||
AccountDataModule::class,
|
||||
ProfileModule::class,
|
||||
SessionAssistedInjectModule::class,
|
||||
|
|
|
@ -54,7 +54,7 @@ import javax.net.ssl.HttpsURLConnection
|
|||
@SessionScope
|
||||
internal class DefaultIdentityService @Inject constructor(
|
||||
private val identityServiceStore: IdentityServiceStore,
|
||||
private val openIdTokenTask: GetOpenIdTokenTask,
|
||||
private val getOpenIdTokenTask: GetOpenIdTokenTask,
|
||||
private val bulkLookupTask: BulkLookupTask,
|
||||
private val identityRegisterTask: IdentityRegisterTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
|
@ -210,7 +210,7 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
private suspend fun getIdentityServerToken(url: String) {
|
||||
val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
|
||||
|
||||
val openIdToken = openIdTokenTask.execute(Unit)
|
||||
val openIdToken = getOpenIdTokenTask.execute(Unit)
|
||||
val token = identityRegisterTask.execute(IdentityRegisterTask.Params(api, openIdToken))
|
||||
|
||||
identityServiceStore.setToken(token.token)
|
||||
|
|
|
@ -31,5 +31,6 @@ abstract class UserAccountData : AccountDataContent {
|
|||
const val TYPE_WIDGETS = "m.widgets"
|
||||
const val TYPE_PUSH_RULES = "m.push_rules"
|
||||
const val TYPE_IDENTITY_SERVER = "m.identity_server"
|
||||
const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataAcceptedTerms(
|
||||
@Json(name = "type") override val type: String = TYPE_ACCEPTED_TERMS,
|
||||
@Json(name = "content") val content: AcceptedTermsContent
|
||||
) : UserAccountData()
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AcceptedTermsContent(
|
||||
@Json(name = "accepted") val acceptedTerms: List<String> = emptyList()
|
||||
)
|
|
@ -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 im.vector.matrix.android.internal.session.terms
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* This class represent a list of urls of terms the user wants to accept
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AcceptTermsBody(
|
||||
@Json(name = "user_accepts")
|
||||
val acceptedTermUrls: List<String>
|
||||
)
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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.matrix.android.internal.session.terms
|
||||
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.terms.GetTermsResponse
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI
|
||||
import im.vector.matrix.android.internal.session.identity.IdentityRegisterTask
|
||||
import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.AcceptedTermsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import okhttp3.OkHttpClient
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultTermsService @Inject constructor(
|
||||
@Unauthenticated
|
||||
private val unauthenticatedOkHttpClient: Lazy<OkHttpClient>,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val termsAPI: TermsAPI,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val getOpenIdTokenTask: GetOpenIdTokenTask,
|
||||
private val identityRegisterTask: IdentityRegisterTask,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||
) : TermsService {
|
||||
override fun getTerms(serviceType: TermsService.ServiceType,
|
||||
baseUrl: String,
|
||||
callback: MatrixCallback<GetTermsResponse>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val sep = if (baseUrl.endsWith("/")) "" else "/"
|
||||
|
||||
val url = when (serviceType) {
|
||||
TermsService.ServiceType.IntegrationManager -> "$baseUrl$sep${NetworkConstants.URI_INTEGRATION_MANAGER_PATH}"
|
||||
TermsService.ServiceType.IdentityService -> "$baseUrl$sep${NetworkConstants.URI_IDENTITY_PATH_V2}"
|
||||
}
|
||||
|
||||
val termsResponse = executeRequest<TermsResponse>(null) {
|
||||
apiCall = termsAPI.getTerms("${url}terms")
|
||||
}
|
||||
|
||||
GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
|
||||
}
|
||||
}
|
||||
|
||||
override fun agreeToTerms(serviceType: TermsService.ServiceType,
|
||||
baseUrl: String,
|
||||
agreedUrls: List<String>,
|
||||
token: String?,
|
||||
callback: MatrixCallback<Unit>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val sep = if (baseUrl.endsWith("/")) "" else "/"
|
||||
|
||||
val url = when (serviceType) {
|
||||
TermsService.ServiceType.IntegrationManager -> "$baseUrl$sep${NetworkConstants.URI_INTEGRATION_MANAGER_PATH}"
|
||||
TermsService.ServiceType.IdentityService -> "$baseUrl$sep${NetworkConstants.URI_IDENTITY_PATH_V2}"
|
||||
}
|
||||
|
||||
val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl)
|
||||
|
||||
executeRequest<Unit>(null) {
|
||||
apiCall = termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse")
|
||||
}
|
||||
|
||||
//client SHOULD update this account data section adding any the URLs
|
||||
// of any additional documents that the user agreed to this list.
|
||||
//Get current m.accepted_terms append new ones and update account data
|
||||
val listOfAcceptedTerms = getAlreadyAcceptedTermUrlsFromAccountData()
|
||||
|
||||
val newList = listOfAcceptedTerms.toMutableSet().apply { addAll(agreedUrls) }.toList()
|
||||
|
||||
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.AcceptedTermsParams(
|
||||
acceptedTermsContent = AcceptedTermsContent(newList)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getToken(url: String): String {
|
||||
// TODO This is duplicated code see DefaultIdentityService
|
||||
val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
|
||||
|
||||
val openIdToken = getOpenIdTokenTask.execute(Unit)
|
||||
val token = identityRegisterTask.execute(IdentityRegisterTask.Params(api, openIdToken))
|
||||
|
||||
return token.token
|
||||
}
|
||||
|
||||
private fun getAlreadyAcceptedTermUrlsFromAccountData(): Set<String> {
|
||||
return accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ACCEPTED_TERMS)
|
||||
?.content
|
||||
?.toModel<AcceptedTermsContent>()
|
||||
?.acceptedTerms
|
||||
?.toSet()
|
||||
.orEmpty()
|
||||
}
|
||||
}
|
|
@ -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 im.vector.matrix.android.internal.session.terms
|
||||
|
||||
import im.vector.matrix.android.internal.network.HttpHeaders
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Url
|
||||
|
||||
internal interface TermsAPI {
|
||||
/**
|
||||
* This request does not require authentication
|
||||
*/
|
||||
@GET
|
||||
fun getTerms(@Url url: String): Call<TermsResponse>
|
||||
|
||||
/**
|
||||
* This request requires authentication
|
||||
*/
|
||||
@POST
|
||||
fun agreeToTerms(@Url url: String, @Body params: AcceptTermsBody, @Header(HttpHeaders.Authorization) token: String): Call<Unit>
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.matrix.android.internal.session.terms
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Lazy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
@Module
|
||||
internal abstract class TermsModule {
|
||||
|
||||
@Module
|
||||
companion object {
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@SessionScope
|
||||
fun providesTermsAPI(@Unauthenticated unauthenticatedOkHttpClient: Lazy<OkHttpClient>,
|
||||
retrofitFactory: RetrofitFactory): TermsAPI {
|
||||
val retrofit = retrofitFactory.create(unauthenticatedOkHttpClient, "https://foo.bar")
|
||||
return retrofit.create(TermsAPI::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindTermsService(service: DefaultTermsService): TermsService
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.matrix.android.internal.session.terms
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
|
||||
|
||||
/**
|
||||
* This class represent a localized privacy policy for registration Flow.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TermsResponse(
|
||||
@Json(name = "policies")
|
||||
val policies: JsonDict? = null
|
||||
) {
|
||||
|
||||
fun getLocalizedTerms(userLanguage: String,
|
||||
defaultLanguage: String = "en"): List<LocalizedFlowDataLoginTerms> {
|
||||
return policies?.map {
|
||||
val tos = policies[it.key] as? Map<*, *> ?: return@map null
|
||||
((tos[userLanguage] ?: tos[defaultLanguage]) as? Map<*, *>)?.let { termsMap ->
|
||||
val name = termsMap[NAME] as? String
|
||||
val url = termsMap[URL] as? String
|
||||
LocalizedFlowDataLoginTerms(
|
||||
policyName = it.key,
|
||||
localizedUrl = url,
|
||||
localizedName = name,
|
||||
version = tos[VERSION] as? String
|
||||
)
|
||||
}
|
||||
}?.filterNotNull() ?: emptyList()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val VERSION = "version"
|
||||
const val NAME = "name"
|
||||
const val URL = "url"
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata
|
|||
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.AcceptedTermsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
|
@ -41,6 +42,15 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||
}
|
||||
}
|
||||
|
||||
data class AcceptedTermsParams(override val type: String = UserAccountData.TYPE_ACCEPTED_TERMS,
|
||||
private val acceptedTermsContent: AcceptedTermsContent
|
||||
) : Params {
|
||||
|
||||
override fun getData(): Any {
|
||||
return acceptedTermsContent
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Use [UserAccountDataDirectMessages] class?
|
||||
data class DirectChatParams(override val type: String = UserAccountData.TYPE_DIRECT_MESSAGES,
|
||||
private val directMessages: Map<String, List<String>>
|
||||
|
|
|
@ -161,6 +161,8 @@
|
|||
android:name=".features.attachments.preview.AttachmentsPreviewActivity"
|
||||
android:theme="@style/AppTheme.AttachmentsPreview" />
|
||||
|
||||
<activity android:name=".features.terms.ReviewTermsActivity" />
|
||||
|
||||
<!-- Services -->
|
||||
|
||||
<service
|
||||
|
|
|
@ -22,7 +22,6 @@ import androidx.fragment.app.FragmentFactory
|
|||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import im.vector.riotx.features.discovery.DiscoverySettingsFragment
|
||||
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment
|
||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
||||
import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment
|
||||
|
@ -42,6 +41,7 @@ import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeF
|
|||
import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment
|
||||
import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment
|
||||
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
|
||||
import im.vector.riotx.features.discovery.DiscoverySettingsFragment
|
||||
import im.vector.riotx.features.discovery.change.SetIdentityServerFragment
|
||||
import im.vector.riotx.features.grouplist.GroupListFragment
|
||||
import im.vector.riotx.features.home.HomeDetailFragment
|
||||
|
@ -98,6 +98,7 @@ import im.vector.riotx.features.share.IncomingShareFragment
|
|||
import im.vector.riotx.features.signout.soft.SoftLogoutFragment
|
||||
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
||||
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
||||
import im.vector.riotx.features.terms.ReviewTermsFragment
|
||||
|
||||
@Module
|
||||
interface FragmentModule {
|
||||
|
@ -486,4 +487,9 @@ interface FragmentModule {
|
|||
@IntoMap
|
||||
@FragmentKey(DiscoverySettingsFragment::class)
|
||||
fun bindDiscoverySettingsFragment(fragment: DiscoverySettingsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(ReviewTermsFragment::class)
|
||||
fun bindReviewTermsFragment(fragment: ReviewTermsFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ import im.vector.riotx.features.settings.VectorSettingsActivity
|
|||
import im.vector.riotx.features.settings.devices.DeviceVerificationInfoBottomSheet
|
||||
import im.vector.riotx.features.share.IncomingShareActivity
|
||||
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
|
||||
import im.vector.riotx.features.terms.ReviewTermsActivity
|
||||
import im.vector.riotx.features.ui.UiStateRepository
|
||||
|
||||
@Component(
|
||||
|
@ -118,6 +119,7 @@ interface ScreenComponent {
|
|||
fun inject(activity: SharedSecureStorageActivity)
|
||||
fun inject(activity: BigImageViewerActivity)
|
||||
fun inject(activity: InviteUsersToRoomActivity)
|
||||
fun inject(activity: ReviewTermsActivity)
|
||||
|
||||
/* ==========================================================================================
|
||||
* BottomSheets
|
||||
|
|
|
@ -70,6 +70,7 @@ import im.vector.riotx.features.settings.VectorPreferences
|
|||
import im.vector.riotx.features.themes.ActivityOtherThemes
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import im.vector.riotx.receivers.DebugReceiver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import timber.log.Timber
|
||||
|
@ -94,6 +95,17 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
protected val viewModelProvider
|
||||
get() = ViewModelProvider(this, viewModelFactory)
|
||||
|
||||
// TODO Other Activity should use this also
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
observer(it)
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* DATA
|
||||
* ========================================================================================== */
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package im.vector.riotx.features.discovery
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
|
@ -23,6 +24,7 @@ import androidx.lifecycle.Observer
|
|||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
|
@ -30,6 +32,8 @@ import im.vector.riotx.core.extensions.exhaustive
|
|||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.discovery.change.SetIdentityServerFragment
|
||||
import im.vector.riotx.features.discovery.change.SetIdentityServerViewModel
|
||||
import im.vector.riotx.features.terms.ReviewTermsActivity
|
||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -86,31 +90,25 @@ class DiscoverySettingsFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
/* TODO
|
||||
if (requestCode == TERMS_REQUEST_CODE) {
|
||||
if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
|
||||
if (Activity.RESULT_OK == resultCode) {
|
||||
viewModel.refreshModel()
|
||||
viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
|
||||
} else {
|
||||
//add some error?
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onSelectIdentityServer() = withState(viewModel) { state ->
|
||||
if (state.termsNotSigned) {
|
||||
/*
|
||||
TODO
|
||||
ReviewTermsActivity.intent(requireContext(),
|
||||
TermsManager.ServiceType.IdentityService,
|
||||
// TODO Use ViewEvents?
|
||||
navigator.openTerms(
|
||||
this,
|
||||
TermsService.ServiceType.IdentityService,
|
||||
SetIdentityServerViewModel.sanitatizeBaseURL(state.identityServer() ?: ""),
|
||||
null).also {
|
||||
startActivityForResult(it, TERMS_REQUEST_CODE)
|
||||
}
|
||||
|
||||
*/
|
||||
null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package im.vector.riotx.features.discovery.change
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
|
@ -30,10 +31,12 @@ import butterknife.OnTextChanged
|
|||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.discovery.DiscoverySharedViewModel
|
||||
import im.vector.riotx.features.terms.ReviewTermsActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
class SetIdentityServerFragment @Inject constructor(
|
||||
|
@ -66,7 +69,7 @@ class SetIdentityServerFragment @Inject constructor(
|
|||
mProgressBar.isVisible = false
|
||||
}
|
||||
val newText = state.newIdentityServer ?: ""
|
||||
if (!newText.equals(mKeyTextEdit.text.toString())) {
|
||||
if (newText != mKeyTextEdit.text.toString()) {
|
||||
mKeyTextEdit.setText(newText)
|
||||
}
|
||||
mKeyInputLayout.error = state.errorMessageId?.let { getString(it) }
|
||||
|
@ -122,29 +125,24 @@ class SetIdentityServerFragment @Inject constructor(
|
|||
}
|
||||
|
||||
is SetIdentityServerViewEvents.ShowTerms -> {
|
||||
/* TODO
|
||||
ReviewTermsActivity.intent(requireContext(),
|
||||
TermsManager.ServiceType.IdentityService,
|
||||
SetIdentityServerViewModel.sanitatizeBaseURL(event.newIdentityServer),
|
||||
null).also {
|
||||
startActivityForResult(it, TERMS_REQUEST_CODE)
|
||||
}
|
||||
*/
|
||||
navigator.openTerms(
|
||||
this,
|
||||
TermsService.ServiceType.IdentityService,
|
||||
SetIdentityServerViewModel.sanitatizeBaseURL(it.newIdentityServer),
|
||||
null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
/* TODO
|
||||
if (requestCode == TERMS_REQUEST_CODE) {
|
||||
if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
|
||||
if (Activity.RESULT_OK == resultCode) {
|
||||
processIdentityServerChange()
|
||||
} else {
|
||||
//add some error?
|
||||
}
|
||||
}
|
||||
*/
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,11 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
|||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.terms.GetTermsResponse
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
@ -102,41 +106,42 @@ class SetIdentityServerViewModel @AssistedInject constructor(
|
|||
}
|
||||
return@withState
|
||||
}
|
||||
// TODO baseUrl = sanitatizeBaseURL(baseUrl)
|
||||
baseUrl = sanitatizeBaseURL(baseUrl)
|
||||
setState {
|
||||
copy(isVerifyingServer = true)
|
||||
}
|
||||
|
||||
/* TODO
|
||||
mxSession.termsManager.get(TermsManager.ServiceType.IdentityService,
|
||||
mxSession.getTerms(TermsService.ServiceType.IdentityService,
|
||||
baseUrl,
|
||||
object : ApiCallback<GetTermsResponse> {
|
||||
override fun onSuccess(info: GetTermsResponse) {
|
||||
object : MatrixCallback<GetTermsResponse> {
|
||||
override fun onSuccess(data: GetTermsResponse) {
|
||||
//has all been accepted?
|
||||
setState {
|
||||
copy(isVerifyingServer = false)
|
||||
}
|
||||
val resp = info.serverResponse
|
||||
val resp = data.serverResponse
|
||||
val tos = resp.getLocalizedTerms(userLanguage)
|
||||
if (tos.isEmpty()) {
|
||||
//prompt do not define policy
|
||||
navigateEvent.value = LiveEvent(NavigateEvent.NoTerms)
|
||||
_viewEvents.post(SetIdentityServerViewEvents.NoTerms)
|
||||
} else {
|
||||
val shouldPrompt = tos.any { !info.alreadyAcceptedTermUrls.contains(it.localizedUrl) }
|
||||
val shouldPrompt = tos.any { !data.alreadyAcceptedTermUrls.contains(it.localizedUrl) }
|
||||
if (shouldPrompt) {
|
||||
navigateEvent.value = LiveEvent(NavigateEvent.ShowTerms(baseUrl))
|
||||
_viewEvents.post(SetIdentityServerViewEvents.ShowTerms(baseUrl))
|
||||
} else {
|
||||
navigateEvent.value = LiveEvent(NavigateEvent.TermsAccepted)
|
||||
_viewEvents.post(SetIdentityServerViewEvents.TermsAccepted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUnexpectedError(e: Exception) {
|
||||
if (e is HttpException && e.httpError.httpCode == 404) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
if (failure is Failure.OtherServerError && failure.httpCode == 404) {
|
||||
setState {
|
||||
copy(isVerifyingServer = false)
|
||||
}
|
||||
navigateEvent.value = LiveEvent(NavigateEvent.NoTerms)
|
||||
// 404: Same as NoTerms
|
||||
// TODO Handle the case where identity
|
||||
_viewEvents.post(SetIdentityServerViewEvents.NoTerms)
|
||||
} else {
|
||||
setState {
|
||||
copy(
|
||||
|
@ -146,26 +151,6 @@ class SetIdentityServerViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNetworkError(e: Exception) {
|
||||
setState {
|
||||
copy(
|
||||
isVerifyingServer = false,
|
||||
errorMessageId = R.string.settings_discovery_bad_identity_server
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMatrixError(e: MatrixError) {
|
||||
setState {
|
||||
copy(
|
||||
isVerifyingServer = false,
|
||||
errorMessageId = R.string.settings_discovery_bad_identity_server
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package im.vector.riotx.features.login.terms
|
||||
|
||||
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||
import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
|
||||
|
||||
data class LocalizedFlowDataLoginTermsChecked(val localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms,
|
||||
var checked: Boolean = false)
|
||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.riotx.features.login.LoginAction
|
|||
import im.vector.riotx.features.login.LoginViewState
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_login_terms.*
|
||||
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||
import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package im.vector.riotx.features.login.terms
|
||||
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||
import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
|
||||
|
||||
data class LoginTermsViewState(
|
||||
val localizedFlowDataLoginTermsChecked: List<LocalizedFlowDataLoginTermsChecked>
|
||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.riotx.features.login.terms
|
|||
|
||||
import android.view.View
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||
import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
|
||||
import javax.inject.Inject
|
||||
|
||||
class PolicyController @Inject constructor() : TypedEpoxyController<List<LocalizedFlowDataLoginTermsChecked>>() {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package im.vector.riotx.features.login.terms
|
||||
|
||||
import im.vector.matrix.android.api.auth.registration.TermPolicies
|
||||
import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
|
||||
import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
|
||||
|
||||
/**
|
||||
* This method extract the policies from the login terms parameter, regarding the user language.
|
||||
|
|
|
@ -23,8 +23,10 @@ import android.view.View
|
|||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.app.TaskStackBuilder
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
|
@ -52,6 +54,7 @@ import im.vector.riotx.features.roomprofile.RoomProfileActivity
|
|||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import im.vector.riotx.features.terms.ReviewTermsActivity
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -207,6 +210,11 @@ class DefaultNavigator @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun openTerms(fragment: Fragment, serviceType: TermsService.ServiceType, baseUrl: String, token: String?, requestCode: Int) {
|
||||
val intent = ReviewTermsActivity.intent(fragment.requireContext(), serviceType, baseUrl, token)
|
||||
fragment.startActivityForResult(intent, requestCode)
|
||||
}
|
||||
|
||||
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
|
||||
if (buildTask) {
|
||||
val stackBuilder = TaskStackBuilder.create(context)
|
||||
|
|
|
@ -19,10 +19,13 @@ package im.vector.riotx.features.navigation
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import im.vector.riotx.features.terms.ReviewTermsActivity
|
||||
|
||||
interface Navigator {
|
||||
|
||||
|
@ -67,4 +70,6 @@ interface Navigator {
|
|||
fun openRoomProfile(context: Context, roomId: String)
|
||||
|
||||
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem)
|
||||
|
||||
fun openTerms(fragment: Fragment,serviceType: TermsService.ServiceType, baseUrl: String, token: String?, requestCode : Int = ReviewTermsActivity.TERMS_REQUEST_CODE)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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.riotx.features.terms
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.extensions.replaceFragment
|
||||
import im.vector.riotx.core.platform.SimpleFragmentActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
class ReviewTermsActivity : SimpleFragmentActivity() {
|
||||
|
||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||
@Inject lateinit var viewModelFactory: ReviewTermsViewModel.Factory
|
||||
|
||||
private val reviewTermsViewModel: ReviewTermsViewModel by viewModel()
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
super.injectWith(injector)
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
|
||||
if (isFirstCreation()) {
|
||||
replaceFragment(R.id.container, ReviewTermsFragment::class.java)
|
||||
}
|
||||
|
||||
reviewTermsViewModel.termsArgs = intent.getParcelableExtra(EXTRA_INFO) ?: error("Missing parameter")
|
||||
|
||||
reviewTermsViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is ReviewTermsViewEvents.Failure -> {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(errorFormatter.toHumanReadable(it.throwable))
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
if (it.finish) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
.show()
|
||||
Unit
|
||||
}
|
||||
ReviewTermsViewEvents.Success -> {
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TERMS_REQUEST_CODE = 15000
|
||||
private const val EXTRA_INFO = "EXTRA_INFO"
|
||||
|
||||
fun intent(context: Context, serviceType: TermsService.ServiceType, baseUrl: String, token: String?): Intent {
|
||||
return Intent(context, ReviewTermsActivity::class.java).also {
|
||||
it.putExtra(EXTRA_INFO, ServiceTermsArgs(serviceType, baseUrl, token))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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.riotx.features.terms
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.onClick
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.core.utils.openUrlInExternalBrowser
|
||||
import kotlinx.android.synthetic.main.fragment_review_terms.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class ReviewTermsFragment @Inject constructor(
|
||||
private val termsController: TermsController
|
||||
) : VectorBaseFragment(), TermsController.Listener {
|
||||
|
||||
private val reviewTermsViewModel: ReviewTermsViewModel by activityViewModel()
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_review_terms
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
termsController.description = when (reviewTermsViewModel.termsArgs.type) {
|
||||
TermsService.ServiceType.IdentityService -> getString(R.string.terms_description_for_identity_server)
|
||||
TermsService.ServiceType.IntegrationManager -> getString(R.string.terms_description_for_integration_manager)
|
||||
}
|
||||
|
||||
termsController.listener = this
|
||||
reviewTermsRecyclerView.configureWith(termsController)
|
||||
|
||||
reviewTermsAccept.onClick { reviewTermsViewModel.handle(ReviewTermsAction.Accept) }
|
||||
reviewTermsDecline.onClick { activity?.finish() }
|
||||
|
||||
reviewTermsViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is ReviewTermsViewEvents.Failure -> {
|
||||
reviewTermsWaitOverlay.isVisible = false
|
||||
// Dialog is displayed by the Activity
|
||||
}
|
||||
ReviewTermsViewEvents.Success -> {
|
||||
// Handled by the Activity
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
reviewTermsViewModel.handle(ReviewTermsAction.LoadTerms(getString(R.string.resources_language)))
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
reviewTermsRecyclerView.cleanup()
|
||||
termsController.listener = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(reviewTermsViewModel) { state ->
|
||||
when (state.termsList) {
|
||||
is Loading -> {
|
||||
reviewTermsBottomBar.isVisible = false
|
||||
reviewTermsWaitOverlay.isVisible = true
|
||||
}
|
||||
is Success -> {
|
||||
updateState(state.termsList.invoke())
|
||||
reviewTermsWaitOverlay.isVisible = false
|
||||
reviewTermsBottomBar.isVisible = true
|
||||
reviewTermsAccept.isEnabled = state.termsList.invoke().all { it.accepted }
|
||||
}
|
||||
else -> {
|
||||
reviewTermsWaitOverlay.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!reviewTermsWaitOverlay.isVisible) {
|
||||
reviewTermsWaitOverlay.isVisible = state.acceptingTerms is Loading
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateState(terms: List<Term>) {
|
||||
termsController.setData(terms)
|
||||
}
|
||||
|
||||
override fun setChecked(term: Term, isChecked: Boolean) {
|
||||
reviewTermsViewModel.handle(ReviewTermsAction.MarkTermAsAccepted(term.url, isChecked))
|
||||
}
|
||||
|
||||
override fun review(term: Term) {
|
||||
openUrlInExternalBrowser(requireContext(), term.url)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* 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.riotx.features.terms
|
||||
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
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.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.terms.GetTermsResponse
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
import timber.log.Timber
|
||||
|
||||
data class Term(
|
||||
val url: String,
|
||||
val name: String,
|
||||
val version: String? = null,
|
||||
val accepted: Boolean = false
|
||||
)
|
||||
|
||||
data class ReviewTermsViewState(
|
||||
val termsList: Async<List<Term>> = Uninitialized,
|
||||
val acceptingTerms: Async<Unit> = Uninitialized
|
||||
) : MvRxState
|
||||
|
||||
sealed class ReviewTermsAction : VectorViewModelAction {
|
||||
data class LoadTerms(val preferredLanguageCode: String) : ReviewTermsAction()
|
||||
data class MarkTermAsAccepted(val url: String, val accepted: Boolean) : ReviewTermsAction()
|
||||
object Accept : ReviewTermsAction()
|
||||
}
|
||||
|
||||
sealed class ReviewTermsViewEvents : VectorViewEvents {
|
||||
data class Failure(val throwable: Throwable, val finish: Boolean) : ReviewTermsViewEvents()
|
||||
object Success : ReviewTermsViewEvents()
|
||||
}
|
||||
|
||||
class ReviewTermsViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: ReviewTermsViewState,
|
||||
private val session: Session
|
||||
) : VectorViewModel<ReviewTermsViewState, ReviewTermsAction, ReviewTermsViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: ReviewTermsViewState): ReviewTermsViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<ReviewTermsViewModel, ReviewTermsViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: ReviewTermsViewState): ReviewTermsViewModel? {
|
||||
val activity: ReviewTermsActivity = (viewModelContext as ActivityViewModelContext).activity()
|
||||
return activity.viewModelFactory.create(state)
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var termsArgs: ServiceTermsArgs
|
||||
|
||||
override fun handle(action: ReviewTermsAction) {
|
||||
when (action) {
|
||||
is ReviewTermsAction.LoadTerms -> loadTerms(action)
|
||||
is ReviewTermsAction.MarkTermAsAccepted -> markTermAsAccepted(action)
|
||||
ReviewTermsAction.Accept -> acceptTerms()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun markTermAsAccepted(action: ReviewTermsAction.MarkTermAsAccepted) = withState { state ->
|
||||
val newList = state.termsList.invoke()?.map {
|
||||
if (it.url == action.url) {
|
||||
it.copy(accepted = action.accepted)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
if (newList != null) {
|
||||
setState {
|
||||
state.copy(
|
||||
termsList = Success(newList)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun acceptTerms() = withState { state ->
|
||||
val acceptedTerms = state.termsList.invoke() ?: return@withState
|
||||
|
||||
if (acceptedTerms.any { it.accepted.not() }) {
|
||||
// Should not happen
|
||||
_viewEvents.post(ReviewTermsViewEvents.Failure(IllegalStateException("Please accept all terms"), false))
|
||||
return@withState
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
acceptingTerms = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
val agreedUrls = acceptedTerms.map { it.url }
|
||||
|
||||
session.agreeToTerms(
|
||||
termsArgs.type,
|
||||
termsArgs.baseURL,
|
||||
agreedUrls,
|
||||
termsArgs.token,
|
||||
object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
_viewEvents.post(ReviewTermsViewEvents.Success)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "Failed to agree to terms")
|
||||
setState {
|
||||
copy(
|
||||
acceptingTerms = Uninitialized
|
||||
)
|
||||
}
|
||||
_viewEvents.post(ReviewTermsViewEvents.Failure(failure, false))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadTerms(action: ReviewTermsAction.LoadTerms) = withState { state ->
|
||||
if (state.termsList is Loading || state.termsList is Success) {
|
||||
return@withState
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(termsList = Loading())
|
||||
}
|
||||
|
||||
session.getTerms(termsArgs.type, termsArgs.baseURL, object : MatrixCallback<GetTermsResponse> {
|
||||
override fun onSuccess(data: GetTermsResponse) {
|
||||
val terms = data.serverResponse.getLocalizedTerms(action.preferredLanguageCode).map {
|
||||
Term(it.localizedUrl ?: "",
|
||||
it.localizedName ?: "",
|
||||
it.version,
|
||||
accepted = data.alreadyAcceptedTermUrls.contains(it.localizedUrl)
|
||||
)
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
termsList = Success(terms)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "Failed to agree to terms")
|
||||
setState {
|
||||
copy(
|
||||
termsList = Uninitialized
|
||||
)
|
||||
}
|
||||
_viewEvents.post(ReviewTermsViewEvents.Failure(failure, true))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.riotx.features.terms
|
||||
|
||||
import android.os.Parcelable
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class ServiceTermsArgs(
|
||||
val type: TermsService.ServiceType,
|
||||
val baseURL: String,
|
||||
val token: String? = null
|
||||
) : Parcelable
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.riotx.features.terms
|
||||
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_tos)
|
||||
abstract class TermItem : EpoxyModelWithHolder<TermItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var checked: Boolean = false
|
||||
|
||||
@EpoxyAttribute
|
||||
var name: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var description: String? = null
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var clickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.checkbox.isChecked = checked
|
||||
holder.title.text = name
|
||||
holder.description.text = description
|
||||
holder.checkbox.setOnCheckedChangeListener(checkChangeListener)
|
||||
holder.view.setOnClickListener(clickListener)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val checkbox by bind<CheckBox>(R.id.term_accept_checkbox)
|
||||
val title by bind<TextView>(R.id.term_name)
|
||||
val description by bind<TextView>(R.id.term_description)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.riotx.features.terms
|
||||
|
||||
import android.view.View
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.features.discovery.settingsSectionTitle
|
||||
import javax.inject.Inject
|
||||
|
||||
class TermsController @Inject constructor() : TypedEpoxyController<List<Term>>() {
|
||||
|
||||
var description: String? = null
|
||||
var listener: Listener? = null
|
||||
|
||||
override fun buildModels(data: List<Term>?) {
|
||||
data?.let {
|
||||
settingsSectionTitle {
|
||||
id("header")
|
||||
titleResId(R.string.widget_integration_review_terms)
|
||||
}
|
||||
it.forEach { term ->
|
||||
termItem {
|
||||
id(term.url)
|
||||
name(term.name)
|
||||
description(description)
|
||||
checked(term.accepted)
|
||||
|
||||
clickListener(View.OnClickListener { listener?.review(term) })
|
||||
checkChangeListener { _, isChecked ->
|
||||
listener?.setChecked(term, isChecked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO error mgmt
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun setChecked(term: Term, isChecked: Boolean)
|
||||
fun review(term: Term)
|
||||
}
|
||||
}
|
63
vector/src/main/res/layout/fragment_review_terms.xml
Normal file
63
vector/src/main/res/layout/fragment_review_terms.xml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/reviewTermsRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/reviewTermsBottomBar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:listitem="@layout/item_tos" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/reviewTermsBottomBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="4dp"
|
||||
android:gravity="center_vertical|end"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="@dimen/layout_vertical_margin"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="@dimen/layout_vertical_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/reviewTermsDecline"
|
||||
style="@style/VectorButtonStyleText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/decline" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/reviewTermsAccept"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/accept" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/reviewTermsWaitOverlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
54
vector/src/main/res/layout/item_tos.xml
Normal file
54
vector/src/main/res/layout/item_tos.xml
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="16dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/term_accept_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/term_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toStartOf="@id/term_policy_arrow"
|
||||
app:layout_constraintStart_toEndOf="@id/term_accept_checkbox"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Integration Manager" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/term_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
app:layout_constraintEnd_toStartOf="@id/term_policy_arrow"
|
||||
app:layout_constraintStart_toStartOf="@+id/term_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/term_name"
|
||||
tools:text="Use bots, bridges, widget and sticker packs." />
|
||||
|
||||
<!-- Do not use drawableEnd on the TextView because of RTL support -->
|
||||
<ImageView
|
||||
android:id="@+id/term_policy_arrow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:rotationY="@integer/rtl_mirror_flip"
|
||||
android:src="@drawable/ic_material_chevron_right_black"
|
||||
android:tint="?riotx_text_primary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in a new issue