diff --git a/CHANGES.rst b/CHANGES.rst
index 507945b056..ba4754243d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,6 +7,7 @@ MatrixSdk:
Features:
- Make Widget/Integration manager optional (#3224)
+ - Handle terms and condition approval for IntegrationManager (#3225)
Improvements:
-
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 0f7d6618f8..d2a79bf0f8 100755
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -433,6 +433,10 @@
android:name=".activity.SASVerificationActivity"
android:label="@string/title_activity_verify_device"
android:windowSoftInputMode="stateHidden|adjustResize" />
+
getString(R.string.terms_description_for_identity_server)
+ TermsManager.ServiceType.IntegrationManager -> getString(R.string.terms_description_for_integration_manager)
+ }
+
+ termsController = TermsController(description, this)
+ termsList.setController(termsController)
+
+ viewModel.loadTerms(getString(R.string.resources_language))
+
+ viewModel.termsList.observe(this, Observer { terms ->
+ when (terms) {
+ is MxAsync.Loading -> {
+ bottomBar.isVisible = false
+ progressBar.isVisible = true
+ }
+ is MxAsync.Error -> {
+ progressBar.isVisible = false
+ terms.stringResId.let { stringRes ->
+ AlertDialog.Builder(requireActivity())
+ .setMessage(stringRes)
+ .setPositiveButton(R.string.ok) { dialog, which ->
+ activity?.finish()
+ }
+ .show()
+ }
+ }
+ is MxAsync.Success -> {
+ updateState(terms.value)
+ progressBar.isVisible = false
+ bottomBar.isVisible = true
+ acceptButton.isEnabled = terms.value.all { it.accepted }
+ }
+ }
+ })
+
+ viewModel.acceptTerms.observe(this, Observer { request ->
+ when (request) {
+ is MxAsync.Loading -> {
+ progressBar.isVisible = true
+ }
+ is MxAsync.Error -> {
+ progressBar.isVisible = false
+ request.stringResId.let { stringRes ->
+ AlertDialog.Builder(requireActivity())
+ .setMessage(stringRes)
+ .setPositiveButton(R.string.ok, null)
+ .show()
+ }
+ }
+ is MxAsync.Success -> {
+ activity?.setResult(Activity.RESULT_OK)
+ activity?.finish()
+ }
+ }
+ })
+ }
+
+ private fun updateState(terms: List) {
+ termsController.setData(terms)
+ }
+
+ companion object {
+ fun newInstance(): AcceptTermsFragment {
+ return AcceptTermsFragment()
+ }
+ }
+
+
+ override fun setChecked(term: Term, isChecked: Boolean) {
+ viewModel.markTermAsAccepted(term.url, isChecked)
+ }
+
+ override fun review(term: Term) {
+ openUrlInExternalBrowser(this.requireContext(), term.url)
+ }
+
+ @OnClick(R.id.terms_bottom_accept)
+ fun onAcceptButton() {
+ viewModel.acceptTerms()
+ }
+
+
+ @OnClick(R.id.terms_bottom_decline)
+ fun onDeclineButton() {
+ activity?.finish()
+ }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/fragments/terms/AcceptTermsViewModel.kt b/vector/src/main/java/im/vector/fragments/terms/AcceptTermsViewModel.kt
new file mode 100644
index 0000000000..bd92234a44
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/terms/AcceptTermsViewModel.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2019 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.fragments.terms
+
+import android.arch.lifecycle.MutableLiveData
+import android.arch.lifecycle.ViewModel
+import im.vector.R
+import im.vector.util.state.MxAsync
+import org.matrix.androidsdk.MXSession
+import org.matrix.androidsdk.core.Log
+import org.matrix.androidsdk.core.callback.ApiCallback
+import org.matrix.androidsdk.core.model.MatrixError
+import org.matrix.androidsdk.features.terms.GetTermsResponse
+import org.matrix.androidsdk.features.terms.TermsManager
+
+class AcceptTermsViewModel : ViewModel() {
+
+ lateinit var termsArgs: ServiceTermsArgs
+
+ val termsList: MutableLiveData>> = MutableLiveData()
+
+ val acceptTerms: MutableLiveData> = MutableLiveData()
+
+ var mxSession: MXSession? = null
+ var termsManager: TermsManager? = null
+
+ fun markTermAsAccepted(url: String, accepted: Boolean) {
+ termsList.value?.invoke()?.map {
+ if (it.url == url) {
+ it.copy(accepted = accepted)
+ } else it
+ }?.let {
+ termsList.postValue(MxAsync.Success(it))
+ }
+ }
+
+ fun initSession(session: MXSession?) {
+ mxSession = session
+ termsManager = mxSession?.termsManager
+ }
+
+ fun acceptTerms() {
+ val acceptedTerms = termsList.value?.invoke() ?: return
+
+ acceptTerms.postValue(MxAsync.Loading())
+ val agreedUrls = acceptedTerms.map { it.url }
+
+ termsManager?.agreeToTerms(termsArgs.type,
+ termsArgs.baseURL,
+ agreedUrls,
+ termsArgs.token,
+ object : ApiCallback {
+ override fun onSuccess(info: Unit) {
+ acceptTerms.postValue(MxAsync.Success(Unit))
+ }
+
+ override fun onUnexpectedError(e: java.lang.Exception?) {
+ acceptTerms.postValue(MxAsync.Error(R.string.unknown_error))
+ Log.e(LOG_TAG, "Failed to agree to terms ", e)
+ }
+
+ override fun onNetworkError(e: java.lang.Exception?) {
+ acceptTerms.postValue(MxAsync.Error(R.string.unknown_error))
+ Log.e(LOG_TAG, "Failed to agree to terms ", e)
+ }
+
+ override fun onMatrixError(e: MatrixError?) {
+ acceptTerms.postValue(MxAsync.Error(R.string.unknown_error))
+ Log.e(LOG_TAG, "Failed to agree to terms " + e?.message)
+ }
+ }
+ )
+ }
+
+ fun loadTerms(preferredLanguageCode: String) {
+ termsList.postValue(MxAsync.Loading())
+
+ termsManager?.get(termsArgs.type, termsArgs.baseURL, object : ApiCallback {
+ override fun onSuccess(info: GetTermsResponse) {
+
+ val terms = mutableListOf()
+ info.serverResponse.getLocalizedPrivacyPolicies(preferredLanguageCode)?.let {
+ terms.add(
+ Term(it.localizedUrl ?: "",
+ it.localizedName ?: "",
+ it.version,
+ accepted = info.alreadyAcceptedTermUrls.contains(it.localizedUrl)
+ )
+ )
+ }
+ info.serverResponse.getLocalizedTermOfServices(preferredLanguageCode)?.let {
+ terms.add(
+ Term(it.localizedUrl ?: "",
+ it.localizedName ?: "",
+ it.version,
+ accepted = info.alreadyAcceptedTermUrls.contains(it.localizedUrl)
+ )
+ )
+ }
+
+ termsList.postValue(MxAsync.Success(terms))
+ }
+
+
+ override fun onUnexpectedError(e: Exception?) {
+ termsList.postValue(MxAsync.Error(R.string.unknown_error))
+ }
+
+ override fun onNetworkError(e: Exception?) {
+ termsList.postValue(MxAsync.Error(R.string.unknown_error))
+ }
+
+ override fun onMatrixError(e: MatrixError?) {
+ termsList.postValue(MxAsync.Error(R.string.unknown_error))
+ }
+
+ })
+ }
+
+ companion object {
+ private val LOG_TAG = AcceptTermsViewModel::javaClass.name
+ }
+
+}
+
+data class Term(
+ val url: String,
+ val name: String,
+ val version: String? = null,
+ val accepted: Boolean = false
+)
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/fragments/terms/ServiceTermsArgs.kt b/vector/src/main/java/im/vector/fragments/terms/ServiceTermsArgs.kt
new file mode 100644
index 0000000000..3a1ce7c5f8
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/terms/ServiceTermsArgs.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 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.fragments.terms
+
+import android.os.Parcelable
+import kotlinx.android.parcel.Parcelize
+import org.matrix.androidsdk.features.terms.TermsManager
+
+@Parcelize
+data class ServiceTermsArgs(
+ val type: TermsManager.ServiceType,
+ val baseURL: String,
+ val token: String?
+) : Parcelable
diff --git a/vector/src/main/java/im/vector/fragments/terms/TermsController.kt b/vector/src/main/java/im/vector/fragments/terms/TermsController.kt
new file mode 100644
index 0000000000..043a6ff3d5
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/terms/TermsController.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 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.fragments.terms
+
+import android.view.View
+import com.airbnb.epoxy.TypedEpoxyController
+import im.vector.R
+import im.vector.ui.epoxy.genericItemHeader
+
+class TermsController(private val itemDescription: String,
+ private val listener: Listener) : TypedEpoxyController>() {
+
+ override fun buildModels(data: List?) {
+ data?.let {
+ genericItemHeader {
+ id("header")
+ textID(R.string.widget_integration_review_terms)
+ }
+ it.forEach { term ->
+ terms {
+ id(term.url)
+ name(term.name)
+ description(itemDescription)
+ 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)
+ }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/fragments/terms/TermsModel.kt b/vector/src/main/java/im/vector/fragments/terms/TermsModel.kt
new file mode 100644
index 0000000000..b03c6278c4
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/terms/TermsModel.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019 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.fragments.terms
+
+import android.view.View
+import android.widget.CheckBox
+import android.widget.CompoundButton
+import android.widget.TextView
+import butterknife.BindView
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.R
+import im.vector.ui.epoxy.BaseEpoxyHolder
+
+@EpoxyModelClass(layout = R.layout.item_tos)
+abstract class TermsModel : EpoxyModelWithHolder() {
+
+ @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.main.setOnClickListener(clickListener)
+ }
+
+ class Holder : BaseEpoxyHolder() {
+ @BindView(R.id.term_accept_checkbox)
+ lateinit var checkbox: CheckBox
+
+ @BindView(R.id.term_name)
+ lateinit var title: TextView
+
+ @BindView(R.id.term_description)
+ lateinit var description: TextView
+ }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/ui/epoxy/GenericHeaderItem.kt b/vector/src/main/java/im/vector/ui/epoxy/GenericHeaderItem.kt
new file mode 100644
index 0000000000..f1c7664a14
--- /dev/null
+++ b/vector/src/main/java/im/vector/ui/epoxy/GenericHeaderItem.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 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.ui.epoxy
+
+import android.support.annotation.StringRes
+import android.widget.TextView
+import butterknife.BindView
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.R
+
+/**
+ * A generic list item header left aligned with notice color.
+ */
+@EpoxyModelClass(layout = R.layout.item_generic_header)
+abstract class GenericItemHeader : EpoxyModelWithHolder() {
+
+ @EpoxyAttribute
+ var text: String? = null
+
+ @EpoxyAttribute
+ @StringRes
+ var textID: Int? = null
+
+ @EpoxyAttribute
+ var textSizeSp: Float = 15f
+
+ override fun bind(holder: Holder) {
+ if (textID != null) {
+ holder.textView.setText(textID!!)
+ } else {
+ holder.textView.text = text
+ }
+ holder.textView.textSize = textSizeSp
+ }
+
+ class Holder : BaseEpoxyHolder() {
+
+ @BindView(R.id.itemGenericHeaderText)
+ lateinit var textView: TextView
+ }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/util/PreferencesManager.java b/vector/src/main/java/im/vector/util/PreferencesManager.java
index 875205331e..bd9709ccb5 100755
--- a/vector/src/main/java/im/vector/util/PreferencesManager.java
+++ b/vector/src/main/java/im/vector/util/PreferencesManager.java
@@ -290,7 +290,7 @@ public static String getIntegrationManagerJitsiUrl(Context context) {
}
- public static void setIntegrationManagerUrls(Context context, String uiURl, String apiURl, String jitsyUrl) {
+ public static void setIntegrationManagerUrls(Context context, String uiURl, String apiURl, String jitsiUrl) {
if (uiURl != null) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
@@ -304,10 +304,10 @@ public static void setIntegrationManagerUrls(Context context, String uiURl, Stri
.apply();
}
- if (jitsyUrl != null) {
+ if (jitsiUrl != null) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
- .putString(SETTINGS_INTEGRATION_MANAGER_JITSI_URL, jitsyUrl)
+ .putString(SETTINGS_INTEGRATION_MANAGER_JITSI_URL, jitsiUrl)
.apply();
}
}
diff --git a/vector/src/main/java/im/vector/util/state/MxAsync.kt b/vector/src/main/java/im/vector/util/state/MxAsync.kt
new file mode 100644
index 0000000000..df7df24e46
--- /dev/null
+++ b/vector/src/main/java/im/vector/util/state/MxAsync.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 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.util.state
+
+import android.support.annotation.StringRes
+
+sealed class MxAsync {
+
+ open operator fun invoke(): T? = null
+
+ class Loading : MxAsync()
+
+ data class Error(@StringRes val stringResId: Int) : MxAsync()
+
+ data class Success(val value: T) : MxAsync() {
+ override operator fun invoke(): T = value
+ }
+
+}
diff --git a/vector/src/main/java/im/vector/widgets/IntegrationManagerConfig.kt b/vector/src/main/java/im/vector/widgets/IntegrationManagerConfig.kt
index 8ac9790702..d38aeba552 100644
--- a/vector/src/main/java/im/vector/widgets/IntegrationManagerConfig.kt
+++ b/vector/src/main/java/im/vector/widgets/IntegrationManagerConfig.kt
@@ -18,7 +18,7 @@ package im.vector.widgets
/**
* Configuration for an integration manager.
- * By default, it uses URLs defined in the app settings but they can be overidden.
+ * By default, it uses URLs defined in the app settings but they can be overridden.
*/
data class IntegrationManagerConfig(
val uiUrl: String,
diff --git a/vector/src/main/java/im/vector/widgets/WidgetsApi.java b/vector/src/main/java/im/vector/widgets/WidgetsApi.java
index 8108210ced..9658bbabcc 100644
--- a/vector/src/main/java/im/vector/widgets/WidgetsApi.java
+++ b/vector/src/main/java/im/vector/widgets/WidgetsApi.java
@@ -19,6 +19,7 @@
import retrofit2.Call;
import retrofit2.http.Body;
+import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;
@@ -30,4 +31,8 @@ interface WidgetsApi {
*/
@POST("register")
Call