Merge pull request #2831 from vector-im/feature/bma/hs_version

Fetch homeserver type and version and display in a new setting screen and add info in rageshakes
This commit is contained in:
Benoit Marty 2021-02-22 10:29:16 +01:00 committed by GitHub
commit e162ebdf91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 903 additions and 26 deletions

View file

@ -5,7 +5,7 @@ Features ✨:
-
Improvements 🙌:
-
- Fetch homeserver type and version and display in a new setting screen and add info in rageshakes (#2831)
Bugfix 🐛:
-

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2021 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.federation
interface FederationService {
/**
* Get information about the homeserver
*/
suspend fun getFederationVersion(): FederationVersion
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2021 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.federation
/**
* Ref: https://matrix.org/docs/spec/server_server/latest#get-matrix-federation-v1-version
*/
data class FederationVersion(
/**
* Arbitrary name that identify this implementation.
*/
val name: String?,
/**
* Version of this implementation. The version format depends on the implementation.
*/
val version: String?
)

View file

@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.federation.FederationService
import org.matrix.android.sdk.api.pushrules.PushRuleService
import org.matrix.android.sdk.api.session.account.AccountService
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
@ -213,6 +214,11 @@ interface Session :
*/
fun searchService(): SearchService
/**
* Returns the federation service associated with the session
*/
fun federationService(): FederationService
/**
* Returns the third party service associated with the session
*/

View file

@ -21,6 +21,11 @@ package org.matrix.android.sdk.api.session.homeserver
*/
interface HomeServerCapabilitiesService {
/**
* Force a refresh of the stored data
*/
suspend fun refreshHomeServerCapabilities()
/**
* Get the HomeServer capabilities
*/

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2021 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.federation
import org.matrix.android.sdk.api.federation.FederationService
import org.matrix.android.sdk.api.federation.FederationVersion
import javax.inject.Inject
internal class DefaultFederationService @Inject constructor(
private val getFederationVersionTask: GetFederationVersionTask
) : FederationService {
override suspend fun getFederationVersion(): FederationVersion {
return getFederationVersionTask.execute(Unit)
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2021 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.federation
import org.matrix.android.sdk.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.GET
internal interface FederationAPI {
@GET(NetworkConstants.URI_FEDERATION_PATH + "version")
fun getVersion(): Call<FederationGetVersionResult>
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2021 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.federation
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Ref: https://matrix.org/docs/spec/server_server/latest#get-matrix-federation-v1-version
*/
@JsonClass(generateAdapter = true)
internal data class FederationGetVersionResult(
@Json(name = "server")
val server: FederationGetVersionServer?
)
@JsonClass(generateAdapter = true)
internal data class FederationGetVersionServer(
/**
* Arbitrary name that identify this implementation.
*/
@Json(name = "name")
val name: String?,
/**
* Version of this implementation. The version format depends on the implementation.
*/
@Json(name = "version")
val version: String?
)

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2021 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.federation
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.federation.FederationService
import org.matrix.android.sdk.internal.di.Unauthenticated
import org.matrix.android.sdk.internal.network.RetrofitFactory
@Module
internal abstract class FederationModule {
@Module
companion object {
@Provides
@JvmStatic
fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>,
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): FederationAPI {
return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrl).create(FederationAPI::class.java)
}
}
@Binds
abstract fun bindFederationService(service: DefaultFederationService): FederationService
@Binds
abstract fun bindGetFederationVersionTask(task: DefaultGetFederationVersionTask): GetFederationVersionTask
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2021 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.federation
import org.matrix.android.sdk.api.federation.FederationVersion
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetFederationVersionTask : Task<Unit, FederationVersion>
internal class DefaultGetFederationVersionTask @Inject constructor(
private val federationAPI: FederationAPI
) : GetFederationVersionTask {
override suspend fun execute(params: Unit): FederationVersion {
val result = executeRequest<FederationGetVersionResult>(null) {
apiCall = federationAPI.getVersion()
}
return FederationVersion(
name = result.server?.name,
version = result.server?.version
)
}
}

View file

@ -36,4 +36,7 @@ internal object NetworkConstants {
// Integration
const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/"
// Federation
const val URI_FEDERATION_PATH = "_matrix/federation/v1/"
}

View file

@ -22,6 +22,7 @@ import io.realm.RealmConfiguration
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.federation.FederationService
import org.matrix.android.sdk.api.pushrules.PushRuleService
import org.matrix.android.sdk.api.session.InitialSyncProgressService
import org.matrix.android.sdk.api.session.Session
@ -88,6 +89,7 @@ internal class DefaultSession @Inject constructor(
private val groupService: Lazy<GroupService>,
private val userService: Lazy<UserService>,
private val filterService: Lazy<FilterService>,
private val federationService: Lazy<FederationService>,
private val cacheService: Lazy<CacheService>,
private val signOutService: Lazy<SignOutService>,
private val pushRuleService: Lazy<PushRuleService>,
@ -260,6 +262,8 @@ internal class DefaultSession @Inject constructor(
override fun searchService(): SearchService = searchService.get()
override fun federationService(): FederationService = federationService.get()
override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
override fun getOkHttpClient(): OkHttpClient {

View file

@ -27,6 +27,7 @@ import org.matrix.android.sdk.internal.crypto.SendGossipWorker
import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker
import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessageWorker
import org.matrix.android.sdk.internal.di.MatrixComponent
import org.matrix.android.sdk.internal.federation.FederationModule
import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
import org.matrix.android.sdk.internal.session.account.AccountModule
import org.matrix.android.sdk.internal.session.cache.CacheModule
@ -87,6 +88,7 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
AccountDataModule::class,
ProfileModule::class,
AccountModule::class,
FederationModule::class,
CallModule::class,
SearchModule::class,
ThirdPartyModule::class

View file

@ -17,16 +17,23 @@
package org.matrix.android.sdk.internal.session.homeserver
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.di.SessionDatabase
import io.realm.Realm
import javax.inject.Inject
internal class DefaultHomeServerCapabilitiesService @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : HomeServerCapabilitiesService {
internal class DefaultHomeServerCapabilitiesService @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask
) : HomeServerCapabilitiesService {
override suspend fun refreshHomeServerCapabilities() {
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = true))
}
override fun getHomeServerCapabilities(): HomeServerCapabilities {
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->

View file

@ -38,7 +38,11 @@ import timber.log.Timber
import java.util.Date
import javax.inject.Inject
internal interface GetHomeServerCapabilitiesTask : Task<Unit, Unit>
internal interface GetHomeServerCapabilitiesTask : Task<GetHomeServerCapabilitiesTask.Params, Unit> {
data class Params(
val forceRefresh: Boolean
)
}
internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
private val capabilitiesAPI: CapabilitiesAPI,
@ -52,12 +56,14 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
private val userId: String
) : GetHomeServerCapabilitiesTask {
override suspend fun execute(params: Unit) {
var doRequest = false
monarchy.awaitTransaction { realm ->
val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm)
override suspend fun execute(params: GetHomeServerCapabilitiesTask.Params) {
var doRequest = params.forceRefresh
if (!doRequest) {
monarchy.awaitTransaction { realm ->
val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm)
doRequest = homeServerCapabilitiesEntity.lastUpdatedTimestamp + MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS < Date().time
doRequest = homeServerCapabilitiesEntity.lastUpdatedTimestamp + MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS < Date().time
}
}
if (!doRequest) {
@ -123,7 +129,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
}
companion object {
// 8 hours like on Riot Web
// 8 hours like on Element Web
private const val MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS = 8 * 60 * 60 * 1000
}
}

View file

@ -77,7 +77,7 @@ internal class DefaultSyncTask @Inject constructor(
initialSyncProgressService.startTask(R.string.initial_sync_start_importing_account, 100)
}
// Maybe refresh the home server capabilities data we know
getHomeServerCapabilitiesTask.execute(Unit)
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)

View file

@ -48,7 +48,8 @@ print("Get information from " + baseUrl)
items = [
# [Title, URL, True for GET request and False for POST request]
["Well-known", baseUrl + ".well-known/matrix/client", True]
, ["Version", baseUrl + "_matrix/client/versions", True]
, ["API version", baseUrl + "_matrix/client/versions", True]
, ["Homeserver version", baseUrl + "_matrix/federation/v1/version", True]
, ["Login flow", baseUrl + "_matrix/client/r0/login", True]
, ["Registration flow", baseUrl + "_matrix/client/r0/register", False]
# Useless , ["Username availability", baseUrl + "_matrix/client/r0/register/available?username=benoit", True]

View file

@ -23,7 +23,6 @@ import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.longClick
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withId
@ -145,7 +144,7 @@ class UiAllScreensSanityTest {
}
private fun ignoreVerification() {
Thread.sleep(6000)
sleep(6000)
val activity = EspressoHelper.getCurrentActivity()!!
val popup = activity.findViewById<View>(com.tapadoo.alerter.R.id.llAlertBackground)
@ -155,7 +154,7 @@ class UiAllScreensSanityTest {
assertDisplayed(R.id.bottomSheetFragmentContainer)
onView(ViewMatchers.isRoot()).perform(SleepViewAction.sleep(2000))
onView(isRoot()).perform(SleepViewAction.sleep(2000))
clickOn(R.string.skip)
assertDisplayed(R.string.are_you_sure)
@ -206,12 +205,12 @@ class UiAllScreensSanityTest {
// Test quick reaction
longClickOnMessage()
// Add quick reaction
clickOn("👍")
clickOn("\uD83D\uDC4D") // 👍
sleep(1000)
// Open reactions
longClickOn("👍")
longClickOn("\uD83D\uDC4D") // 👍
pressBack()
// Test add reaction
@ -226,6 +225,8 @@ class UiAllScreensSanityTest {
clickOn(R.string.edit)
// TODO Cancel action
writeTo(R.id.composerEditText, "Hello universe!")
// Wait a bit for the keyboard layout to update
sleep(30)
clickOn(R.id.sendButton)
// Open edit history
longClickOnMessage("Hello universe! (edited)")
@ -277,13 +278,18 @@ class UiAllScreensSanityTest {
assertDisplayed(R.id.roomProfileAvatarView)
// Room addresses
// Leave
clickListItem(R.id.matrixProfileRecyclerView, 13)
clickDialogNegativeButton()
// Advanced
// Room addresses
clickListItem(R.id.matrixProfileRecyclerView, 15)
onView(isRoot()).perform(waitForView(withText(R.string.room_alias_published_alias_title)))
pressBack()
// Room permissions
clickListItem(R.id.matrixProfileRecyclerView, 15)
clickListItem(R.id.matrixProfileRecyclerView, 17)
onView(isRoot()).perform(waitForView(withText(R.string.room_permissions_title)))
clickOn(R.string.room_permissions_change_room_avatar)
clickDialogNegativeButton()
@ -292,10 +298,6 @@ class UiAllScreensSanityTest {
clickOn(R.string.hide_advanced)
pressBack()
// Leave
clickListItem(R.id.matrixProfileRecyclerView, 17)
clickDialogNegativeButton()
// Menu share
// clickMenu(R.id.roomProfileShareAction)
// pressBack()
@ -483,6 +485,9 @@ class UiAllScreensSanityTest {
clickOn(R.string.add_identity_server)
pressBack()
pressBack()
// Home server
clickOnPreference(R.string.settings_home_server)
pressBack()
// Identity server
clickOnPreference(R.string.settings_identity_server)
pressBack()

View file

@ -109,6 +109,7 @@ import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailFragmen
import im.vector.app.features.settings.devtools.IncomingKeyRequestListFragment
import im.vector.app.features.settings.devtools.KeyRequestsFragment
import im.vector.app.features.settings.devtools.OutgoingKeyRequestListFragment
import im.vector.app.features.settings.homeserver.HomeserverSettingsFragment
import im.vector.app.features.settings.ignored.VectorSettingsIgnoredUsersFragment
import im.vector.app.features.settings.locale.LocalePickerFragment
import im.vector.app.features.settings.push.PushGatewaysFragment
@ -284,6 +285,11 @@ interface FragmentModule {
@FragmentKey(VectorSettingsLabsFragment::class)
fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(HomeserverSettingsFragment::class)
fun bindHomeserverSettingsFragment(fragment: HomeserverSettingsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(VectorSettingsPinFragment::class)

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2021 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.discovery
import android.widget.ImageView
import androidx.annotation.DrawableRes
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
@EpoxyModelClass(layout = R.layout.item_settings_centered_image)
abstract class SettingsCenteredImageItem : EpoxyModelWithHolder<SettingsCenteredImageItem.Holder>() {
@EpoxyAttribute
@DrawableRes
var drawableRes: Int = 0
override fun bind(holder: Holder) {
super.bind(holder)
holder.image.setImageResource(drawableRes)
}
class Holder : VectorEpoxyHolder() {
val image by bind<ImageView>(R.id.itemSettingsImage)
}
}

View file

@ -21,12 +21,15 @@ import android.view.MenuItem
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import com.airbnb.mvrx.viewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityBugReportBinding
import timber.log.Timber
import javax.inject.Inject
/**
* Form to send a bug report
@ -39,6 +42,10 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
override fun getBinding() = ActivityBugReportBinding.inflate(layoutInflater)
@Inject lateinit var bugReportViewModelFactory: BugReportViewModel.Factory
private val viewModel: BugReportViewModel by viewModel()
private var forSuggestion: Boolean = false
override fun initUiAndData() {
@ -114,7 +121,7 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
/**
* Send the bug report
*/
private fun sendBugReport() {
private fun sendBugReport() = withState(viewModel) { state ->
views.bugReportScrollview.alpha = 0.3f
views.bugReportMaskView.isVisible = true
@ -133,6 +140,7 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
views.bugReportButtonIncludeKeyShareHistory.isChecked,
views.bugReportButtonIncludeScreenshot.isChecked,
views.bugReportEditText.text.toString(),
state.serverVersion,
object : BugReporter.IMXBugReportListener {
override fun onUploadFailed(reason: String?) {
try {

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2021 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.rageshake
import com.airbnb.mvrx.MvRxState
data class BugReportState(
val serverVersion: String = ""
) : MvRxState

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2021 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.rageshake
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
class BugReportViewModel @AssistedInject constructor(
@Assisted initialState: BugReportState,
val activeSessionHolder: ActiveSessionHolder
) : VectorViewModel<BugReportState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedFactory
interface Factory {
fun create(initialState: BugReportState): BugReportViewModel
}
companion object : MvRxViewModelFactory<BugReportViewModel, BugReportState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: BugReportState): BugReportViewModel? {
val activity: BugReportActivity = (viewModelContext as ActivityViewModelContext).activity()
return activity.bugReportViewModelFactory.create(state)
}
}
init {
fetchHomeserverVersion()
}
private fun fetchHomeserverVersion() {
viewModelScope.launch {
val version = tryOrNull {
activeSessionHolder.getSafeActiveSession()
?.federationService()
?.getFederationVersion()
?.let { "${it.name} - ${it.version}" }
} ?: "undefined"
setState {
copy(
serverVersion = version
)
}
}
}
override fun handle(action: EmptyAction) {
// No op
}
}

View file

@ -165,6 +165,7 @@ class BugReporter @Inject constructor(
withKeyRequestHistory: Boolean,
withScreenshot: Boolean,
theBugDescription: String,
serverVersion: String,
listener: IMXBugReportListener?) {
// enumerate files to delete
val mBugReportFiles: MutableList<File> = ArrayList()
@ -273,6 +274,7 @@ class BugReporter @Inject constructor(
.addFormDataPart("app_language", VectorLocale.applicationLocale.toString())
.addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
.addFormDataPart("server_version", serverVersion)
val buildNumber = context.getString(R.string.build_number)
if (buildNumber.isNotEmpty() && buildNumber != "0") {

View file

@ -57,6 +57,7 @@ class RoomProfileController @Inject constructor(
fun onRoomAliasesClicked()
fun onRoomPermissionsClicked()
fun onRoomIdClicked()
fun onRoomDevToolsClicked()
fun onUrlInTopicLongClicked(url: String)
}
@ -193,7 +194,7 @@ class RoomProfileController @Inject constructor(
title = stringProvider.getString(R.string.room_settings_permissions_title),
subtitle = stringProvider.getString(R.string.room_settings_permissions_subtitle),
dividerColor = dividerColor,
divider = true,
divider = false,
editable = true,
action = { callback?.onRoomPermissionsClicked() }
)
@ -204,10 +205,29 @@ class RoomProfileController @Inject constructor(
title = stringProvider.getString(R.string.room_settings_room_internal_id),
subtitle = roomSummary.roomId,
dividerColor = dividerColor,
divider = false,
divider = true,
editable = false,
action = { callback?.onRoomIdClicked() }
)
data.roomCreateContent()?.roomVersion?.let {
buildProfileAction(
id = "roomVersion",
title = stringProvider.getString(R.string.room_settings_room_version_title),
subtitle = it,
dividerColor = dividerColor,
divider = true,
editable = false
)
}
buildProfileAction(
id = "devTools",
title = stringProvider.getString(R.string.dev_tools_menu_name),
subtitle = roomSummary.roomId,
dividerColor = dividerColor,
divider = false,
editable = true,
action = { callback?.onRoomDevToolsClicked() }
)
}
}

View file

@ -295,6 +295,10 @@ class RoomProfileFragment @Inject constructor(
copyToClipboard(requireContext(), roomProfileArgs.roomId)
}
override fun onRoomDevToolsClicked() {
navigator.openDevTools(requireContext(), roomProfileArgs.roomId)
}
override fun onUrlInTopicLongClicked(url: String) {
copyToClipboard(requireContext(), url, true)
}

View file

@ -33,13 +33,17 @@ import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.state.isPublic
import org.matrix.android.sdk.rx.RxRoom
import org.matrix.android.sdk.rx.mapOptional
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap
@ -69,10 +73,20 @@ class RoomProfileViewModel @AssistedInject constructor(
init {
val rxRoom = room.rx()
observeRoomSummary(rxRoom)
observeRoomCreateContent(rxRoom)
observeBannedRoomMembers(rxRoom)
observePermissions()
}
private fun observeRoomCreateContent(rxRoom: RxRoom) {
rxRoom.liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition)
.mapOptional { it.content.toModel<RoomCreateContent>() }
.unwrap()
.execute {
copy(roomCreateContent = it)
}
}
private fun observeRoomSummary(rxRoom: RxRoom) {
rxRoom.liveRoomSummary()
.unwrap()

View file

@ -22,10 +22,12 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
data class RoomProfileViewState(
val roomId: String,
val roomSummary: Async<RoomSummary> = Uninitialized,
val roomCreateContent: Async<RoomCreateContent> = Uninitialized,
val bannedMembership: Async<List<RoomMemberSummary>> = Uninitialized,
val actionPermissions: ActionPermissions = ActionPermissions(),
val isLoading: Boolean = false

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2021 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.settings.homeserver
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.federation.FederationVersion
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
data class HomeServerSettingsViewState(
val baseUrl: String = "",
val homeServerCapabilities: HomeServerCapabilities = HomeServerCapabilities(),
val federationVersion: Async<FederationVersion> = Uninitialized
) : MvRxState

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2021 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.settings.homeserver
import im.vector.app.core.platform.VectorViewModelAction
sealed class HomeserverSettingsAction : VectorViewModelAction {
object Refresh : HomeserverSettingsAction()
}

View file

@ -0,0 +1,120 @@
/*
* Copyright (c) 2021 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.settings.homeserver
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.app.R
import im.vector.app.core.epoxy.errorWithRetryItem
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.settingsCenteredImageItem
import im.vector.app.features.discovery.settingsInfoItem
import im.vector.app.features.discovery.settingsSectionTitleItem
import org.matrix.android.sdk.api.federation.FederationVersion
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import javax.inject.Inject
class HomeserverSettingsController @Inject constructor(
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter
) : TypedEpoxyController<HomeServerSettingsViewState>() {
var callback: Callback? = null
interface Callback {
fun retry()
}
override fun buildModels(data: HomeServerSettingsViewState?) {
data ?: return
buildHeader(data)
when (val federationVersion = data.federationVersion) {
is Loading,
is Uninitialized ->
loadingItem {
id("loading")
}
is Fail ->
errorWithRetryItem {
id("error")
text(errorFormatter.toHumanReadable(federationVersion.error))
listener { callback?.retry() }
}
is Success ->
buildFederationVersion(federationVersion())
}
buildCapabilities(data)
}
private fun buildHeader(state: HomeServerSettingsViewState) {
settingsCenteredImageItem {
id("icon")
drawableRes(R.drawable.ic_layers)
}
settingsSectionTitleItem {
id("urlTitle")
titleResId(R.string.hs_url)
}
settingsInfoItem {
id("urlValue")
helperText(state.baseUrl)
}
}
private fun buildFederationVersion(federationVersion: FederationVersion) {
settingsSectionTitleItem {
id("nameTitle")
titleResId(R.string.settings_server_name)
}
settingsInfoItem {
id("nameValue")
helperText(federationVersion.name)
}
settingsSectionTitleItem {
id("versionTitle")
titleResId(R.string.settings_server_version)
}
settingsInfoItem {
id("versionValue")
helperText(federationVersion.version)
}
}
private fun buildCapabilities(data: HomeServerSettingsViewState) {
settingsSectionTitleItem {
id("uploadTitle")
titleResId(R.string.settings_server_upload_size_title)
}
val limit = data.homeServerCapabilities.maxUploadFileSize
settingsInfoItem {
id("uploadValue")
if (limit == HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN) {
helperTextResId(R.string.settings_server_upload_size_unknown)
} else {
helperText(stringProvider.getString(R.string.settings_server_upload_size_content, "${limit / 1048576L} MB"))
}
}
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2021 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.settings.homeserver
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import javax.inject.Inject
/**
* Display some information about the homeserver
*/
class HomeserverSettingsFragment @Inject constructor(
val homeserverSettingsViewModelFactory: HomeserverSettingsViewModel.Factory,
private val homeserverSettingsController: HomeserverSettingsController
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(),
HomeserverSettingsController.Callback {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding {
return FragmentGenericRecyclerBinding.inflate(inflater, container, false)
}
private val viewModel: HomeserverSettingsViewModel by fragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeserverSettingsController.callback = this
views.genericRecyclerView.configureWith(homeserverSettingsController)
}
override fun onDestroyView() {
homeserverSettingsController.callback = null
views.genericRecyclerView.cleanup()
super.onDestroyView()
}
override fun onResume() {
super.onResume()
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.settings_home_server)
}
override fun retry() {
viewModel.handle(HomeserverSettingsAction.Refresh)
}
override fun invalidate() = withState(viewModel) { state ->
homeserverSettingsController.setData(state)
}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2021 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.settings.homeserver
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
class HomeserverSettingsViewModel @AssistedInject constructor(
@Assisted initialState: HomeServerSettingsViewState,
private val session: Session
) : VectorViewModel<HomeServerSettingsViewState, HomeserverSettingsAction, EmptyViewEvents>(initialState) {
@AssistedFactory
interface Factory {
fun create(initialState: HomeServerSettingsViewState): HomeserverSettingsViewModel
}
companion object : MvRxViewModelFactory<HomeserverSettingsViewModel, HomeServerSettingsViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: HomeServerSettingsViewState): HomeserverSettingsViewModel? {
val fragment: HomeserverSettingsFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.homeserverSettingsViewModelFactory.create(state)
}
}
init {
setState {
copy(
baseUrl = session.sessionParams.homeServerUrl,
homeServerCapabilities = session.getHomeServerCapabilities()
)
}
fetchHomeserverVersion()
refreshHomeServerCapabilities()
}
private fun refreshHomeServerCapabilities() {
viewModelScope.launch {
runCatching {
session.refreshHomeServerCapabilities()
}
setState {
copy(
homeServerCapabilities = session.getHomeServerCapabilities()
)
}
}
}
private fun fetchHomeserverVersion() {
setState {
copy(
federationVersion = Loading()
)
}
viewModelScope.launch {
try {
val federationVersion = session.federationService().getFederationVersion()
setState {
copy(
federationVersion = Success(federationVersion)
)
}
} catch (failure: Throwable) {
setState {
copy(
federationVersion = Fail(failure)
)
}
}
}
}
override fun handle(action: HomeserverSettingsAction) {
when (action) {
HomeserverSettingsAction.Refresh -> fetchHomeserverVersion()
}
}
}

View file

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:pathData="M12,2L2,7L12,12L22,7L12,2Z"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M2,17L12,22L22,17"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M2,12L12,17L22,12"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView 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:id="@+id/itemSettingsImage"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_margin="16dp"
android:importantForAccessibility="no"
app:tint="@color/riotx_accent"
tools:src="@drawable/ic_layers" />

View file

@ -1117,6 +1117,7 @@
<!-- advanced -->
<string name="room_settings_category_advanced_title">Advanced</string>
<string name="room_settings_room_internal_id">This rooms internal ID</string>
<string name="room_settings_room_version_title">Room version</string>
<string name="room_settings_addresses_pref_title">Addresses</string>
<string name="room_settings_labs_pref_title">Labs</string>
<string name="room_settings_labs_warning_message">These are experimental features that may break in unexpected ways. Use with caution.</string>
@ -2336,6 +2337,12 @@
<string name="settings_active_sessions_manage">Manage Sessions</string>
<string name="settings_active_sessions_signout_device">Sign out of this session</string>
<string name="settings_server_name">Server name</string>
<string name="settings_server_version">Server version</string>
<string name="settings_server_upload_size_title">Server file upload limit</string>
<string name="settings_server_upload_size_content">Your homeserver accepts attachments (files, media, etc.) with a size up to %s.</string>
<string name="settings_server_upload_size_unknown">The limit is unknown.</string>
<string name="settings_failed_to_get_crypto_device_info">No cryptographic information available</string>
<string name="settings_active_sessions_verified_device_desc">This session is trusted for secure messaging because you verified it:</string>

View file

@ -81,6 +81,7 @@
<im.vector.app.core.preference.VectorPreference
android:key="SETTINGS_HOME_SERVER_PREFERENCE_KEY"
android:title="@string/settings_home_server"
app:fragment="im.vector.app.features.settings.homeserver.HomeserverSettingsFragment"
tools:summary="https://homeserver.org" />
<im.vector.app.core.preference.VectorPreference