Merge branch 'develop' into feature/ons/fix_email_confirmation_flow
This commit is contained in:
commit
1f4a360a0c
|
@ -2,6 +2,7 @@ Changes in Element 1.0.9 (2020-XX-XX)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
|
- Search messages in a room - phase 1 (#2110)
|
||||||
- Hide encrypted history (before user is invited). Can be shown if wanted in developer settings
|
- Hide encrypted history (before user is invited). Can be shown if wanted in developer settings
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
|
@ -11,6 +12,7 @@ Improvements 🙌:
|
||||||
- Allow user to reset cross signing if he has no way to recover (#2052)
|
- Allow user to reset cross signing if he has no way to recover (#2052)
|
||||||
- Create home shortcut for any room (#1525)
|
- Create home shortcut for any room (#1525)
|
||||||
- Can't confirm email due to killing by Android (#2021)
|
- Can't confirm email due to killing by Android (#2021)
|
||||||
|
- Filter room member (and banned users) by name (#2184)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Improve support for image/audio/video/file selection with intent changes (#1376)
|
- Improve support for image/audio/video/file selection with intent changes (#1376)
|
||||||
|
@ -20,7 +22,7 @@ Translations 🗣:
|
||||||
-
|
-
|
||||||
|
|
||||||
SDK API changes ⚠️:
|
SDK API changes ⚠️:
|
||||||
-
|
- Search messages in a room by using Session.searchService() or Room.search()
|
||||||
|
|
||||||
Build 🧱:
|
Build 🧱:
|
||||||
- Use Update Gradle Wrapper Action
|
- Use Update Gradle Wrapper Action
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.session.search
|
||||||
|
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Assert.fail
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||||
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class SearchMessagesTest : InstrumentedTest {
|
||||||
|
|
||||||
|
private val MESSAGE = "Lorem ipsum dolor sit amet"
|
||||||
|
|
||||||
|
private val commonTestHelper = CommonTestHelper(context())
|
||||||
|
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun sendTextMessageAndSearchPartOfItUsingSession() {
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val aliceRoomId = cryptoTestData.roomId
|
||||||
|
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||||
|
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||||
|
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10))
|
||||||
|
aliceTimeline.start()
|
||||||
|
|
||||||
|
commonTestHelper.sendTextMessage(
|
||||||
|
roomFromAlicePOV,
|
||||||
|
MESSAGE,
|
||||||
|
2)
|
||||||
|
|
||||||
|
run {
|
||||||
|
var lock = CountDownLatch(1)
|
||||||
|
|
||||||
|
val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||||
|
snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
aliceTimeline.addListener(eventListener)
|
||||||
|
commonTestHelper.await(lock)
|
||||||
|
|
||||||
|
lock = CountDownLatch(1)
|
||||||
|
aliceSession
|
||||||
|
.searchService()
|
||||||
|
.search(
|
||||||
|
searchTerm = "lore",
|
||||||
|
limit = 10,
|
||||||
|
includeProfile = true,
|
||||||
|
afterLimit = 0,
|
||||||
|
beforeLimit = 10,
|
||||||
|
orderByRecent = true,
|
||||||
|
nextBatch = null,
|
||||||
|
roomId = aliceRoomId,
|
||||||
|
callback = object : MatrixCallback<SearchResult> {
|
||||||
|
override fun onSuccess(data: SearchResult) {
|
||||||
|
super.onSuccess(data)
|
||||||
|
assertTrue(data.results?.size == 2)
|
||||||
|
assertTrue(
|
||||||
|
data.results
|
||||||
|
?.all {
|
||||||
|
(it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
|
||||||
|
}.orFalse()
|
||||||
|
)
|
||||||
|
lock.countDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
super.onFailure(failure)
|
||||||
|
fail(failure.localizedMessage)
|
||||||
|
lock.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
|
aliceTimeline.removeAllListeners()
|
||||||
|
cryptoTestData.cleanUp(commonTestHelper)
|
||||||
|
}
|
||||||
|
|
||||||
|
aliceSession.startSync(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun sendTextMessageAndSearchPartOfItUsingRoom() {
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val aliceRoomId = cryptoTestData.roomId
|
||||||
|
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||||
|
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||||
|
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10))
|
||||||
|
aliceTimeline.start()
|
||||||
|
|
||||||
|
commonTestHelper.sendTextMessage(
|
||||||
|
roomFromAlicePOV,
|
||||||
|
MESSAGE,
|
||||||
|
2)
|
||||||
|
|
||||||
|
run {
|
||||||
|
var lock = CountDownLatch(1)
|
||||||
|
|
||||||
|
val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||||
|
snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
aliceTimeline.addListener(eventListener)
|
||||||
|
commonTestHelper.await(lock)
|
||||||
|
|
||||||
|
lock = CountDownLatch(1)
|
||||||
|
roomFromAlicePOV
|
||||||
|
.search(
|
||||||
|
searchTerm = "lore",
|
||||||
|
limit = 10,
|
||||||
|
includeProfile = true,
|
||||||
|
afterLimit = 0,
|
||||||
|
beforeLimit = 10,
|
||||||
|
orderByRecent = true,
|
||||||
|
nextBatch = null,
|
||||||
|
callback = object : MatrixCallback<SearchResult> {
|
||||||
|
override fun onSuccess(data: SearchResult) {
|
||||||
|
super.onSuccess(data)
|
||||||
|
assertTrue(data.results?.size == 2)
|
||||||
|
assertTrue(
|
||||||
|
data.results
|
||||||
|
?.all {
|
||||||
|
(it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
|
||||||
|
}.orFalse()
|
||||||
|
)
|
||||||
|
lock.countDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
super.onFailure(failure)
|
||||||
|
fail(failure.localizedMessage)
|
||||||
|
lock.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
|
aliceTimeline.removeAllListeners()
|
||||||
|
cryptoTestData.cleanUp(commonTestHelper)
|
||||||
|
}
|
||||||
|
|
||||||
|
aliceSession.startSync(true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.matrix.android.sdk">
|
package="org.matrix.android.sdk">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
@ -7,6 +8,15 @@
|
||||||
|
|
||||||
<application android:networkSecurityConfig="@xml/network_security_config">
|
<application android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is mandatory to run integration tests
|
||||||
|
-->
|
||||||
|
<provider
|
||||||
|
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||||
|
android:authorities="${applicationId}.workmanager-init"
|
||||||
|
android:exported="false"
|
||||||
|
tools:node="remove" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
The SDK offers a secured File provider to access downloaded files.
|
The SDK offers a secured File provider to access downloaded files.
|
||||||
Access to these file will be given via the FileService, with a temporary
|
Access to these file will be given via the FileService, with a temporary
|
||||||
|
|
|
@ -41,6 +41,7 @@ import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||||
import org.matrix.android.sdk.api.session.pushers.PushersService
|
import org.matrix.android.sdk.api.session.pushers.PushersService
|
||||||
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
|
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
|
||||||
import org.matrix.android.sdk.api.session.room.RoomService
|
import org.matrix.android.sdk.api.session.room.RoomService
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||||
import org.matrix.android.sdk.api.session.signout.SignOutService
|
import org.matrix.android.sdk.api.session.signout.SignOutService
|
||||||
|
@ -201,6 +202,11 @@ interface Session :
|
||||||
*/
|
*/
|
||||||
fun permalinkService(): PermalinkService
|
fun permalinkService(): PermalinkService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the search service associated with the session
|
||||||
|
*/
|
||||||
|
fun searchService(): SearchService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener to the session.
|
* Add a listener to the session.
|
||||||
* @param listener the listener to add.
|
* @param listener the listener to add.
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.matrix.android.sdk.api.session.room
|
package org.matrix.android.sdk.api.session.room
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.room.call.RoomCallService
|
import org.matrix.android.sdk.api.session.room.call.RoomCallService
|
||||||
import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
|
import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
|
||||||
import org.matrix.android.sdk.api.session.room.members.MembershipService
|
import org.matrix.android.sdk.api.session.room.members.MembershipService
|
||||||
|
@ -33,6 +34,8 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
||||||
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
||||||
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||||
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,4 +72,25 @@ interface Room :
|
||||||
* A current snapshot of [RoomSummary] associated with the room
|
* A current snapshot of [RoomSummary] associated with the room
|
||||||
*/
|
*/
|
||||||
fun roomSummary(): RoomSummary?
|
fun roomSummary(): RoomSummary?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic function to search a term in a room.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#module-search
|
||||||
|
* @param searchTerm the term to search
|
||||||
|
* @param nextBatch the token that retrieved from the previous response. Should be provided to get the next batch of results
|
||||||
|
* @param orderByRecent if true, the most recent message events will return in the first places of the list
|
||||||
|
* @param limit the maximum number of events to return.
|
||||||
|
* @param beforeLimit how many events before the result are returned.
|
||||||
|
* @param afterLimit how many events after the result are returned.
|
||||||
|
* @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
|
||||||
|
* @param callback Callback to get the search result
|
||||||
|
*/
|
||||||
|
fun search(searchTerm: String,
|
||||||
|
nextBatch: String?,
|
||||||
|
orderByRecent: Boolean,
|
||||||
|
limit: Int,
|
||||||
|
beforeLimit: Int,
|
||||||
|
afterLimit: Int,
|
||||||
|
includeProfile: Boolean,
|
||||||
|
callback: MatrixCallback<SearchResult>): Cancelable
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.session.search
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain class to represent the response of a search request in a room.
|
||||||
|
*/
|
||||||
|
data class SearchResult(
|
||||||
|
/**
|
||||||
|
* Token that can be used to get the next batch of results, by passing as the next_batch parameter to the next call.
|
||||||
|
* If this field is null, there are no more results.
|
||||||
|
*/
|
||||||
|
val nextBatch: String? = null,
|
||||||
|
/**
|
||||||
|
* List of words which should be highlighted, useful for stemming which may change the query terms.
|
||||||
|
*/
|
||||||
|
val highlights: List<String>? = null,
|
||||||
|
/**
|
||||||
|
* List of results in the requested order.
|
||||||
|
*/
|
||||||
|
val results: List<EventAndSender>? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class EventAndSender(
|
||||||
|
val event: Event,
|
||||||
|
val sender: MatrixItem.UserItem?
|
||||||
|
)
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.session.search
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines methods to search messages in rooms.
|
||||||
|
*/
|
||||||
|
interface SearchService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic function to search a term in a room.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#module-search
|
||||||
|
* @param searchTerm the term to search
|
||||||
|
* @param roomId the roomId to search term inside
|
||||||
|
* @param nextBatch the token that retrieved from the previous response. Should be provided to get the next batch of results
|
||||||
|
* @param orderByRecent if true, the most recent message events will return in the first places of the list
|
||||||
|
* @param limit the maximum number of events to return.
|
||||||
|
* @param beforeLimit how many events before the result are returned.
|
||||||
|
* @param afterLimit how many events after the result are returned.
|
||||||
|
* @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
|
||||||
|
* @param callback Callback to get the search result
|
||||||
|
*/
|
||||||
|
fun search(searchTerm: String,
|
||||||
|
roomId: String,
|
||||||
|
nextBatch: String?,
|
||||||
|
orderByRecent: Boolean,
|
||||||
|
limit: Int,
|
||||||
|
beforeLimit: Int,
|
||||||
|
afterLimit: Int,
|
||||||
|
includeProfile: Boolean,
|
||||||
|
callback: MatrixCallback<SearchResult>): Cancelable
|
||||||
|
}
|
|
@ -49,6 +49,7 @@ import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||||
import org.matrix.android.sdk.api.session.pushers.PushersService
|
import org.matrix.android.sdk.api.session.pushers.PushersService
|
||||||
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
|
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
|
||||||
import org.matrix.android.sdk.api.session.room.RoomService
|
import org.matrix.android.sdk.api.session.room.RoomService
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||||
import org.matrix.android.sdk.api.session.signout.SignOutService
|
import org.matrix.android.sdk.api.session.signout.SignOutService
|
||||||
|
@ -95,6 +96,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val pushRuleService: Lazy<PushRuleService>,
|
private val pushRuleService: Lazy<PushRuleService>,
|
||||||
private val pushersService: Lazy<PushersService>,
|
private val pushersService: Lazy<PushersService>,
|
||||||
private val termsService: Lazy<TermsService>,
|
private val termsService: Lazy<TermsService>,
|
||||||
|
private val searchService: Lazy<SearchService>,
|
||||||
private val cryptoService: Lazy<DefaultCryptoService>,
|
private val cryptoService: Lazy<DefaultCryptoService>,
|
||||||
private val defaultFileService: Lazy<FileService>,
|
private val defaultFileService: Lazy<FileService>,
|
||||||
private val permalinkService: Lazy<PermalinkService>,
|
private val permalinkService: Lazy<PermalinkService>,
|
||||||
|
@ -264,6 +266,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
|
|
||||||
override fun callSignalingService(): CallSignalingService = callSignalingService.get()
|
override fun callSignalingService(): CallSignalingService = callSignalingService.get()
|
||||||
|
|
||||||
|
override fun searchService(): SearchService = searchService.get()
|
||||||
|
|
||||||
override fun getOkHttpClient(): OkHttpClient {
|
override fun getOkHttpClient(): OkHttpClient {
|
||||||
return unauthenticatedWithCertificateOkHttpClient.get()
|
return unauthenticatedWithCertificateOkHttpClient.get()
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ import org.matrix.android.sdk.internal.session.room.send.EncryptEventWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
|
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
|
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
|
||||||
|
import org.matrix.android.sdk.internal.session.search.SearchModule
|
||||||
import org.matrix.android.sdk.internal.session.signout.SignOutModule
|
import org.matrix.android.sdk.internal.session.signout.SignOutModule
|
||||||
import org.matrix.android.sdk.internal.session.sync.SyncModule
|
import org.matrix.android.sdk.internal.session.sync.SyncModule
|
||||||
import org.matrix.android.sdk.internal.session.sync.SyncTask
|
import org.matrix.android.sdk.internal.session.sync.SyncTask
|
||||||
|
@ -86,7 +87,8 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
ProfileModule::class,
|
ProfileModule::class,
|
||||||
SessionAssistedInjectModule::class,
|
SessionAssistedInjectModule::class,
|
||||||
AccountModule::class,
|
AccountModule::class,
|
||||||
CallModule::class
|
CallModule::class,
|
||||||
|
SearchModule::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
|
|
@ -36,10 +36,13 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
||||||
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
||||||
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||||
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
||||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
|
import org.matrix.android.sdk.internal.session.search.SearchTask
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
import org.matrix.android.sdk.internal.task.configureWith
|
||||||
import java.security.InvalidParameterException
|
import java.security.InvalidParameterException
|
||||||
|
@ -62,7 +65,8 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
private val roomMembersService: MembershipService,
|
private val roomMembersService: MembershipService,
|
||||||
private val roomPushRuleService: RoomPushRuleService,
|
private val roomPushRuleService: RoomPushRuleService,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val sendStateTask: SendStateTask) :
|
private val sendStateTask: SendStateTask,
|
||||||
|
private val searchTask: SearchTask) :
|
||||||
Room,
|
Room,
|
||||||
TimelineService by timelineService,
|
TimelineService by timelineService,
|
||||||
SendService by sendService,
|
SendService by sendService,
|
||||||
|
@ -123,4 +127,27 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun search(searchTerm: String,
|
||||||
|
nextBatch: String?,
|
||||||
|
orderByRecent: Boolean,
|
||||||
|
limit: Int,
|
||||||
|
beforeLimit: Int,
|
||||||
|
afterLimit: Int,
|
||||||
|
includeProfile: Boolean,
|
||||||
|
callback: MatrixCallback<SearchResult>): Cancelable {
|
||||||
|
return searchTask
|
||||||
|
.configureWith(SearchTask.Params(
|
||||||
|
searchTerm = searchTerm,
|
||||||
|
roomId = roomId,
|
||||||
|
nextBatch = nextBatch,
|
||||||
|
orderByRecent = orderByRecent,
|
||||||
|
limit = limit,
|
||||||
|
beforeLimit = beforeLimit,
|
||||||
|
afterLimit = afterLimit,
|
||||||
|
includeProfile = includeProfile
|
||||||
|
)) {
|
||||||
|
this.callback = callback
|
||||||
|
}.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService
|
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService
|
||||||
import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
|
import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
|
||||||
import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
|
import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
|
||||||
|
import org.matrix.android.sdk.internal.session.search.SearchTask
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -59,7 +60,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
|
||||||
private val membershipServiceFactory: DefaultMembershipService.Factory,
|
private val membershipServiceFactory: DefaultMembershipService.Factory,
|
||||||
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
|
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val sendStateTask: SendStateTask) :
|
private val sendStateTask: SendStateTask,
|
||||||
|
private val searchTask: SearchTask) :
|
||||||
RoomFactory {
|
RoomFactory {
|
||||||
|
|
||||||
override fun create(roomId: String): Room {
|
override fun create(roomId: String): Room {
|
||||||
|
@ -81,7 +83,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
|
||||||
roomMembersService = membershipServiceFactory.create(roomId),
|
roomMembersService = membershipServiceFactory.create(roomId),
|
||||||
roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
|
roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
|
||||||
taskExecutor = taskExecutor,
|
taskExecutor = taskExecutor,
|
||||||
sendStateTask = sendStateTask
|
sendStateTask = sendStateTask,
|
||||||
|
searchTask = searchTask
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchService
|
||||||
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
import javax.inject.Inject
|
||||||
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
|
import org.matrix.android.sdk.internal.task.configureWith
|
||||||
|
|
||||||
|
internal class DefaultSearchService @Inject constructor(
|
||||||
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val searchTask: SearchTask
|
||||||
|
) : SearchService {
|
||||||
|
|
||||||
|
override fun search(searchTerm: String,
|
||||||
|
roomId: String,
|
||||||
|
nextBatch: String?,
|
||||||
|
orderByRecent: Boolean,
|
||||||
|
limit: Int,
|
||||||
|
beforeLimit: Int,
|
||||||
|
afterLimit: Int,
|
||||||
|
includeProfile: Boolean,
|
||||||
|
callback: MatrixCallback<SearchResult>): Cancelable {
|
||||||
|
return searchTask
|
||||||
|
.configureWith(SearchTask.Params(
|
||||||
|
searchTerm = searchTerm,
|
||||||
|
roomId = roomId,
|
||||||
|
nextBatch = nextBatch,
|
||||||
|
orderByRecent = orderByRecent,
|
||||||
|
limit = limit,
|
||||||
|
beforeLimit = beforeLimit,
|
||||||
|
afterLimit = afterLimit,
|
||||||
|
includeProfile = includeProfile
|
||||||
|
)) {
|
||||||
|
this.callback = callback
|
||||||
|
}.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||||
|
import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody
|
||||||
|
import org.matrix.android.sdk.internal.session.search.response.SearchResponse
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
internal interface SearchAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a full text search across different categories.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-search
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "search")
|
||||||
|
fun search(@Query("next_batch") nextBatch: String?,
|
||||||
|
@Body body: SearchRequestBody): Call<SearchResponse>
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchService
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
@Module
|
||||||
|
internal abstract class SearchModule {
|
||||||
|
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
@Provides
|
||||||
|
@JvmStatic
|
||||||
|
@SessionScope
|
||||||
|
fun providesSearchAPI(retrofit: Retrofit): SearchAPI {
|
||||||
|
return retrofit.create(SearchAPI::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSearchService(service: DefaultSearchService): SearchService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSearchTask(task: DefaultSearchTask): SearchTask
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.matrix.android.sdk.api.session.search.EventAndSender
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody
|
||||||
|
import org.matrix.android.sdk.internal.session.search.request.SearchRequestCategories
|
||||||
|
import org.matrix.android.sdk.internal.session.search.request.SearchRequestEventContext
|
||||||
|
import org.matrix.android.sdk.internal.session.search.request.SearchRequestFilter
|
||||||
|
import org.matrix.android.sdk.internal.session.search.request.SearchRequestOrder
|
||||||
|
import org.matrix.android.sdk.internal.session.search.request.SearchRequestRoomEvents
|
||||||
|
import org.matrix.android.sdk.internal.session.search.response.SearchResponse
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface SearchTask : Task<SearchTask.Params, SearchResult> {
|
||||||
|
|
||||||
|
data class Params(
|
||||||
|
val searchTerm: String,
|
||||||
|
val roomId: String,
|
||||||
|
val nextBatch: String? = null,
|
||||||
|
val orderByRecent: Boolean,
|
||||||
|
val limit: Int,
|
||||||
|
val beforeLimit: Int,
|
||||||
|
val afterLimit: Int,
|
||||||
|
val includeProfile: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSearchTask @Inject constructor(
|
||||||
|
private val searchAPI: SearchAPI,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : SearchTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: SearchTask.Params): SearchResult {
|
||||||
|
return executeRequest<SearchResponse>(eventBus) {
|
||||||
|
val searchRequestBody = SearchRequestBody(
|
||||||
|
searchCategories = SearchRequestCategories(
|
||||||
|
roomEvents = SearchRequestRoomEvents(
|
||||||
|
searchTerm = params.searchTerm,
|
||||||
|
orderBy = if (params.orderByRecent) SearchRequestOrder.RECENT else SearchRequestOrder.RANK,
|
||||||
|
filter = SearchRequestFilter(
|
||||||
|
limit = params.limit,
|
||||||
|
rooms = listOf(params.roomId)
|
||||||
|
),
|
||||||
|
eventContext = SearchRequestEventContext(
|
||||||
|
beforeLimit = params.beforeLimit,
|
||||||
|
afterLimit = params.afterLimit,
|
||||||
|
includeProfile = params.includeProfile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
apiCall = searchAPI.search(params.nextBatch, searchRequestBody)
|
||||||
|
}.toDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SearchResponse.toDomain(): SearchResult {
|
||||||
|
return SearchResult(
|
||||||
|
nextBatch = searchCategories.roomEvents?.nextBatch,
|
||||||
|
highlights = searchCategories.roomEvents?.highlights,
|
||||||
|
results = searchCategories.roomEvents?.results?.map { searchResponseItem ->
|
||||||
|
EventAndSender(
|
||||||
|
searchResponseItem.event,
|
||||||
|
searchResponseItem.event.senderId?.let { senderId ->
|
||||||
|
searchResponseItem.context?.profileInfo?.get(senderId)
|
||||||
|
?.let {
|
||||||
|
MatrixItem.UserItem(
|
||||||
|
senderId,
|
||||||
|
it["displayname"] as? String,
|
||||||
|
it["avatar_url"] as? String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}?.reversed()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.request
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchRequestBody(
|
||||||
|
/**
|
||||||
|
* Required. Describes which categories to search in and their criteria.
|
||||||
|
*/
|
||||||
|
@Json(name = "search_categories")
|
||||||
|
val searchCategories: SearchRequestCategories
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.request
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchRequestCategories(
|
||||||
|
/**
|
||||||
|
* Mapping of category name to search criteria.
|
||||||
|
*/
|
||||||
|
@Json(name = "room_events")
|
||||||
|
val roomEvents: SearchRequestRoomEvents? = null
|
||||||
|
)
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.request
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchRequestEventContext(
|
||||||
|
// How many events before the result are returned.
|
||||||
|
@Json(name = "before_limit")
|
||||||
|
val beforeLimit: Int? = null,
|
||||||
|
// How many events after the result are returned.
|
||||||
|
@Json(name = "after_limit")
|
||||||
|
val afterLimit: Int? = null,
|
||||||
|
// Requests that the server returns the historic profile information
|
||||||
|
@Json(name = "include_profile")
|
||||||
|
val includeProfile: Boolean? = null
|
||||||
|
)
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.request
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchRequestFilter(
|
||||||
|
// The maximum number of events to return.
|
||||||
|
@Json(name = "limit")
|
||||||
|
val limit: Int? = null,
|
||||||
|
// A list of room IDs to include. If this list is absent then all rooms are included.
|
||||||
|
@Json(name = "rooms")
|
||||||
|
val rooms: List<String>? = null
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.request
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the order in which to search for results.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
|
internal enum class SearchRequestOrder {
|
||||||
|
@Json(name = "rank") RANK,
|
||||||
|
@Json(name = "recent") RECENT
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.request
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchRequestRoomEvents(
|
||||||
|
/**
|
||||||
|
* Required. The string to search events for.
|
||||||
|
*/
|
||||||
|
@Json(name = "search_term")
|
||||||
|
val searchTerm: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keys to search. Defaults to all. One of: ["content.body", "content.name", "content.topic"]
|
||||||
|
*/
|
||||||
|
@Json(name = "keys")
|
||||||
|
val keys: Any? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This takes a filter.
|
||||||
|
*/
|
||||||
|
@Json(name = "filter")
|
||||||
|
val filter: SearchRequestFilter? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The order in which to search for results. By default, this is "rank". One of: ["recent", "rank"]
|
||||||
|
*/
|
||||||
|
@Json(name = "order_by")
|
||||||
|
val orderBy: SearchRequestOrder? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures whether any context for the events returned are included in the response.
|
||||||
|
*/
|
||||||
|
@Json(name = "event_context")
|
||||||
|
val eventContext: SearchRequestEventContext? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the server return the current state for each room returned.
|
||||||
|
*/
|
||||||
|
@Json(name = "include_state")
|
||||||
|
val include_state: Boolean? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that the server partitions the result set based on the provided list of keys.
|
||||||
|
*/
|
||||||
|
// val groupings: SearchRequestGroupings? = null
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.response
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchResponse(
|
||||||
|
/**
|
||||||
|
* Required. Describes which categories to search in and their criteria.
|
||||||
|
*/
|
||||||
|
@Json(name = "search_categories")
|
||||||
|
val searchCategories: SearchResponseCategories
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.response
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchResponseCategories(
|
||||||
|
/**
|
||||||
|
* Mapping of category name to search criteria.
|
||||||
|
*/
|
||||||
|
@Json(name = "room_events")
|
||||||
|
val roomEvents: SearchResponseRoomEvents? = null
|
||||||
|
)
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.response
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchResponseEventContext(
|
||||||
|
// Events just before the result.
|
||||||
|
@Json(name = "events_before")
|
||||||
|
val eventsBefore: List<Event>,
|
||||||
|
// Events just after the result.
|
||||||
|
@Json(name = "events_after")
|
||||||
|
val eventsAfter: List<Event>,
|
||||||
|
// Pagination token for the start of the chunk
|
||||||
|
@Json(name = "start")
|
||||||
|
val start: String? = null,
|
||||||
|
// Pagination token for the end of the chunk
|
||||||
|
@Json(name = "end")
|
||||||
|
val end: String? = null,
|
||||||
|
// The historic profile information of the users that sent the events returned. The string key is the user ID for which the profile belongs to.
|
||||||
|
@Json(name = "profile_info")
|
||||||
|
val profileInfo: Map<String, JsonDict>? = null
|
||||||
|
)
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.response
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchResponseItem(
|
||||||
|
/**
|
||||||
|
* A number that describes how closely this result matches the search. Higher is closer.
|
||||||
|
*/
|
||||||
|
@Json(name = "rank")
|
||||||
|
val rank: Double? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event that matched.
|
||||||
|
*/
|
||||||
|
@Json(name = "result")
|
||||||
|
val event: Event,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context for result, if requested.
|
||||||
|
*/
|
||||||
|
@Json(name = "context")
|
||||||
|
val context: SearchResponseEventContext? = null
|
||||||
|
)
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.search.response
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchResponseRoomEvents(
|
||||||
|
/**
|
||||||
|
* List of results in the requested order.
|
||||||
|
*/
|
||||||
|
@Json(name = "results")
|
||||||
|
val results: List<SearchResponseItem>? = null,
|
||||||
|
@Json(name = "count")
|
||||||
|
val count: Int? = null,
|
||||||
|
/**
|
||||||
|
* List of words which should be highlighted, useful for stemming which may change the query terms.
|
||||||
|
*/
|
||||||
|
@Json(name = "highlights")
|
||||||
|
val highlights: List<String>? = null,
|
||||||
|
/**
|
||||||
|
* Token that can be used to get the next batch of results, by passing as the next_batch parameter to the next call.
|
||||||
|
* If this field is absent, there are no more results.
|
||||||
|
*/
|
||||||
|
@Json(name = "next_batch")
|
||||||
|
val nextBatch: String? = null
|
||||||
|
)
|
|
@ -164,7 +164,7 @@ Formatter\.formatShortFileSize===1
|
||||||
# android\.text\.TextUtils
|
# android\.text\.TextUtils
|
||||||
|
|
||||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If it is ok, change the value in file forbidden_strings_in_code.txt
|
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If it is ok, change the value in file forbidden_strings_in_code.txt
|
||||||
enum class===80
|
enum class===81
|
||||||
|
|
||||||
### Do not import temporary legacy classes
|
### Do not import temporary legacy classes
|
||||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||||
|
|
|
@ -219,6 +219,7 @@
|
||||||
<activity android:name=".features.terms.ReviewTermsActivity" />
|
<activity android:name=".features.terms.ReviewTermsActivity" />
|
||||||
<activity android:name=".features.widgets.WidgetActivity" />
|
<activity android:name=".features.widgets.WidgetActivity" />
|
||||||
<activity android:name=".features.pin.PinActivity" />
|
<activity android:name=".features.pin.PinActivity" />
|
||||||
|
<activity android:name=".features.home.room.detail.search.SearchActivity" />
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ import im.vector.app.features.home.HomeDrawerFragment
|
||||||
import im.vector.app.features.home.LoadingFragment
|
import im.vector.app.features.home.LoadingFragment
|
||||||
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
|
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailFragment
|
import im.vector.app.features.home.room.detail.RoomDetailFragment
|
||||||
|
import im.vector.app.features.home.room.detail.search.SearchFragment
|
||||||
import im.vector.app.features.home.room.list.RoomListFragment
|
import im.vector.app.features.home.room.list.RoomListFragment
|
||||||
import im.vector.app.features.login.LoginCaptchaFragment
|
import im.vector.app.features.login.LoginCaptchaFragment
|
||||||
import im.vector.app.features.login.LoginFragment
|
import im.vector.app.features.login.LoginFragment
|
||||||
|
@ -570,4 +571,9 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(RoomBannedMemberListFragment::class)
|
@FragmentKey(RoomBannedMemberListFragment::class)
|
||||||
fun bindRoomBannedMemberListFragment(fragment: RoomBannedMemberListFragment): Fragment
|
fun bindRoomBannedMemberListFragment(fragment: RoomBannedMemberListFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(SearchFragment::class)
|
||||||
|
fun bindSearchFragment(fragment: SearchFragment): Fragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import im.vector.app.features.home.HomeActivity
|
||||||
import im.vector.app.features.home.HomeModule
|
import im.vector.app.features.home.HomeModule
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||||
import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
||||||
|
import im.vector.app.features.home.room.detail.search.SearchActivity
|
||||||
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
||||||
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
||||||
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
|
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
|
||||||
|
@ -142,6 +143,7 @@ interface ScreenComponent {
|
||||||
fun inject(activity: VectorCallActivity)
|
fun inject(activity: VectorCallActivity)
|
||||||
fun inject(activity: VectorAttachmentViewerActivity)
|
fun inject(activity: VectorAttachmentViewerActivity)
|
||||||
fun inject(activity: VectorJitsiActivity)
|
fun inject(activity: VectorJitsiActivity)
|
||||||
|
fun inject(activity: SearchActivity)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* BottomSheets
|
* BottomSheets
|
||||||
|
|
|
@ -673,10 +673,22 @@ class RoomDetailFragment @Inject constructor(
|
||||||
roomDetailViewModel.handle(RoomDetailAction.EndCall)
|
roomDetailViewModel.handle(RoomDetailAction.EndCall)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.search -> {
|
||||||
|
handleSearchAction()
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSearchAction() {
|
||||||
|
if (session.getRoom(roomDetailArgs.roomId)?.isEncrypted() == false) {
|
||||||
|
navigator.openSearch(requireContext(), roomDetailArgs.roomId)
|
||||||
|
} else {
|
||||||
|
showDialogWithMessage(getString(R.string.search_is_not_supported_in_e2e_room))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleCallRequest(item: MenuItem) = withState(roomDetailViewModel) { state ->
|
private fun handleCallRequest(item: MenuItem) = withState(roomDetailViewModel) { state ->
|
||||||
val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState
|
val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState
|
||||||
val isVideoCall = item.itemId == R.id.video_call
|
val isVideoCall = item.itemId == R.id.video_call
|
||||||
|
|
|
@ -538,6 +538,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
R.id.voice_call,
|
R.id.voice_call,
|
||||||
R.id.video_call -> true // always show for discoverability
|
R.id.video_call -> true // always show for discoverability
|
||||||
R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null
|
R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null
|
||||||
|
R.id.search -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.search
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed class SearchAction : VectorViewModelAction {
|
||||||
|
data class SearchWith(val searchTerm: String) : SearchAction()
|
||||||
|
object LoadMore : SearchAction()
|
||||||
|
object Retry : SearchAction()
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.search
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import com.airbnb.mvrx.MvRx
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.di.ScreenComponent
|
||||||
|
import im.vector.app.core.extensions.addFragment
|
||||||
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
|
import kotlinx.android.synthetic.main.activity_search.*
|
||||||
|
|
||||||
|
class SearchActivity : VectorBaseActivity() {
|
||||||
|
|
||||||
|
private val searchFragment: SearchFragment?
|
||||||
|
get() {
|
||||||
|
return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? SearchFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutRes() = R.layout.activity_search
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
super.injectWith(injector)
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
configureToolbar(searchToolbar)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initUiAndData() {
|
||||||
|
if (isFirstCreation()) {
|
||||||
|
val fragmentArgs: SearchArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
|
||||||
|
addFragment(R.id.searchFragmentContainer, SearchFragment::class.java, fragmentArgs, FRAGMENT_TAG)
|
||||||
|
}
|
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
|
searchFragment?.search(query)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Open the keyboard immediately
|
||||||
|
searchView.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val FRAGMENT_TAG = "SearchFragment"
|
||||||
|
|
||||||
|
fun newIntent(context: Context, args: SearchArgs): Intent {
|
||||||
|
return Intent(context, SearchActivity::class.java).apply {
|
||||||
|
// If we do that we will have the same room two times on the stack. Let's allow infinite stack for the moment.
|
||||||
|
// flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
|
||||||
|
putExtra(MvRx.KEY_ARG, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.search
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.args
|
||||||
|
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.extensions.hideKeyboard
|
||||||
|
import im.vector.app.core.extensions.trackItemsVisibilityChange
|
||||||
|
import im.vector.app.core.platform.StateView
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import kotlinx.android.synthetic.main.fragment_search.*
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class SearchArgs(
|
||||||
|
val roomId: String
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
class SearchFragment @Inject constructor(
|
||||||
|
val viewModelFactory: SearchViewModel.Factory,
|
||||||
|
private val controller: SearchResultController
|
||||||
|
) : VectorBaseFragment(), StateView.EventCallback, SearchResultController.Listener {
|
||||||
|
|
||||||
|
private val fragmentArgs: SearchArgs by args()
|
||||||
|
private val searchViewModel: SearchViewModel by fragmentViewModel()
|
||||||
|
|
||||||
|
private var pendingScrollToPosition: Int? = null
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_search
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
stateView.contentView = searchResultRecycler
|
||||||
|
stateView.eventCallback = this
|
||||||
|
|
||||||
|
configureRecyclerView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureRecyclerView() {
|
||||||
|
searchResultRecycler.trackItemsVisibilityChange()
|
||||||
|
searchResultRecycler.configureWith(controller, showDivider = false)
|
||||||
|
(searchResultRecycler.layoutManager as? LinearLayoutManager)?.stackFromEnd = true
|
||||||
|
controller.listener = this
|
||||||
|
|
||||||
|
controller.addModelBuildListener {
|
||||||
|
pendingScrollToPosition?.let {
|
||||||
|
searchResultRecycler.smoothScrollToPosition(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
searchResultRecycler?.cleanup()
|
||||||
|
controller.listener = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(searchViewModel) { state ->
|
||||||
|
if (state.searchResult.isNullOrEmpty()) {
|
||||||
|
when (state.asyncSearchRequest) {
|
||||||
|
is Loading -> {
|
||||||
|
stateView.state = StateView.State.Loading
|
||||||
|
}
|
||||||
|
is Fail -> {
|
||||||
|
stateView.state = StateView.State.Error(errorFormatter.toHumanReadable(state.asyncSearchRequest.error))
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
stateView.state = StateView.State.Empty(
|
||||||
|
title = getString(R.string.search_no_results),
|
||||||
|
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_search_no_results))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pendingScrollToPosition = (state.lastBatchSize - 1).coerceAtLeast(0)
|
||||||
|
|
||||||
|
stateView.state = StateView.State.Content
|
||||||
|
controller.setData(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun search(query: String) {
|
||||||
|
view?.hideKeyboard()
|
||||||
|
searchViewModel.handle(SearchAction.SearchWith(query))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRetryClicked() {
|
||||||
|
searchViewModel.handle(SearchAction.Retry)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClicked(event: Event) {
|
||||||
|
event.roomId?.let {
|
||||||
|
navigator.openRoom(requireContext(), it, event.eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadMore() {
|
||||||
|
searchViewModel.handle(SearchAction.LoadMore)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.search
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import com.airbnb.epoxy.VisibilityState
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import im.vector.app.core.epoxy.loadingItem
|
||||||
|
import im.vector.app.core.ui.list.genericItemHeader
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.search.EventAndSender
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
import java.util.Calendar
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SearchResultController @Inject constructor(
|
||||||
|
private val session: Session,
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val dateFormatter: VectorDateFormatter
|
||||||
|
) : TypedEpoxyController<SearchViewState>() {
|
||||||
|
|
||||||
|
var listener: Listener? = null
|
||||||
|
|
||||||
|
private var idx = 0
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onItemClicked(event: Event)
|
||||||
|
fun loadMore()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
setData(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildModels(data: SearchViewState?) {
|
||||||
|
data ?: return
|
||||||
|
|
||||||
|
if (data.hasMoreResult) {
|
||||||
|
loadingItem {
|
||||||
|
// Always use a different id, because we can be notified several times of visibility state changed
|
||||||
|
id("loadMore${idx++}")
|
||||||
|
onVisibilityStateChanged { _, _, visibilityState ->
|
||||||
|
if (visibilityState == VisibilityState.VISIBLE) {
|
||||||
|
listener?.loadMore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSearchResultItems(data.searchResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildSearchResultItems(events: List<EventAndSender>) {
|
||||||
|
var lastDate: Calendar? = null
|
||||||
|
|
||||||
|
events.forEach { eventAndSender ->
|
||||||
|
val eventDate = Calendar.getInstance().apply {
|
||||||
|
timeInMillis = eventAndSender.event.originServerTs ?: System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
if (lastDate?.get(Calendar.DAY_OF_YEAR) != eventDate.get(Calendar.DAY_OF_YEAR)) {
|
||||||
|
genericItemHeader {
|
||||||
|
id(eventDate.hashCode())
|
||||||
|
text(dateFormatter.format(eventDate.timeInMillis, DateFormatKind.EDIT_HISTORY_HEADER))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastDate = eventDate
|
||||||
|
|
||||||
|
searchResultItem {
|
||||||
|
id(eventAndSender.event.eventId)
|
||||||
|
avatarRenderer(avatarRenderer)
|
||||||
|
dateFormatter(dateFormatter)
|
||||||
|
event(eventAndSender.event)
|
||||||
|
sender(eventAndSender.sender
|
||||||
|
?: eventAndSender.event.senderId?.let { session.getUser(it) }?.toMatrixItem())
|
||||||
|
listener { listener?.onItemClicked(eventAndSender.event) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.search
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.epoxy.onClick
|
||||||
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_search_result)
|
||||||
|
abstract class SearchResultItem : VectorEpoxyModel<SearchResultItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
@EpoxyAttribute var dateFormatter: VectorDateFormatter? = null
|
||||||
|
@EpoxyAttribute lateinit var event: Event
|
||||||
|
@EpoxyAttribute var sender: MatrixItem? = null
|
||||||
|
@EpoxyAttribute var listener: ClickListener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
|
||||||
|
holder.view.onClick(listener)
|
||||||
|
sender?.let { avatarRenderer.render(it, holder.avatarImageView) }
|
||||||
|
holder.memberNameView.setTextOrHide(sender?.getBestName())
|
||||||
|
holder.timeView.text = dateFormatter?.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
|
||||||
|
// TODO Improve that (use formattedBody, etc.)
|
||||||
|
holder.contentView.text = event.content?.get("body") as? String
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||||
|
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||||
|
val timeView by bind<TextView>(R.id.messageTimeView)
|
||||||
|
val contentView by bind<TextView>(R.id.messageContentView)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.search
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
sealed class SearchViewEvents : VectorViewEvents {
|
||||||
|
data class Failure(val throwable: Throwable) : SearchViewEvents()
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.search
|
||||||
|
|
||||||
|
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 com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.extensions.exhaustive
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||||
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||||
|
|
||||||
|
class SearchViewModel @AssistedInject constructor(
|
||||||
|
@Assisted private val initialState: SearchViewState,
|
||||||
|
session: Session
|
||||||
|
) : VectorViewModel<SearchViewState, SearchAction, SearchViewEvents>(initialState) {
|
||||||
|
|
||||||
|
private var room: Room? = session.getRoom(initialState.roomId)
|
||||||
|
|
||||||
|
private var currentTask: Cancelable? = null
|
||||||
|
|
||||||
|
private var nextBatch: String? = null
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: SearchViewState): SearchViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<SearchViewModel, SearchViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: SearchViewState): SearchViewModel? {
|
||||||
|
val fragment: SearchFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.viewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: SearchAction) {
|
||||||
|
when (action) {
|
||||||
|
is SearchAction.SearchWith -> handleSearchWith(action)
|
||||||
|
is SearchAction.LoadMore -> handleLoadMore()
|
||||||
|
is SearchAction.Retry -> handleRetry()
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSearchWith(action: SearchAction.SearchWith) {
|
||||||
|
if (action.searchTerm.isNotEmpty()) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
searchResult = emptyList(),
|
||||||
|
hasMoreResult = false,
|
||||||
|
lastBatchSize = 0,
|
||||||
|
searchTerm = action.searchTerm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
startSearching(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleLoadMore() {
|
||||||
|
startSearching(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRetry() {
|
||||||
|
startSearching(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startSearching(isNextBatch: Boolean) = withState { state ->
|
||||||
|
if (state.searchTerm == null) return@withState
|
||||||
|
|
||||||
|
// There is no batch to retrieve
|
||||||
|
if (isNextBatch && nextBatch == null) return@withState
|
||||||
|
|
||||||
|
// Show full screen loading just for the clean search
|
||||||
|
if (!isNextBatch) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncSearchRequest = Loading()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTask?.cancel()
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val result = awaitCallback<SearchResult> {
|
||||||
|
currentTask = room?.search(
|
||||||
|
searchTerm = state.searchTerm,
|
||||||
|
nextBatch = nextBatch,
|
||||||
|
orderByRecent = true,
|
||||||
|
beforeLimit = 0,
|
||||||
|
afterLimit = 0,
|
||||||
|
includeProfile = true,
|
||||||
|
limit = 20,
|
||||||
|
callback = it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onSearchResultSuccess(result)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
if (failure is Failure.Cancelled) return@launch
|
||||||
|
|
||||||
|
_viewEvents.post(SearchViewEvents.Failure(failure))
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncSearchRequest = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSearchResultSuccess(searchResult: SearchResult) = withState { state ->
|
||||||
|
val accumulatedResult = searchResult.results.orEmpty().plus(state.searchResult)
|
||||||
|
|
||||||
|
// Note: We do not care about the highlights for the moment, but it will be the same algorithm
|
||||||
|
|
||||||
|
nextBatch = searchResult.nextBatch
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
searchResult = accumulatedResult,
|
||||||
|
hasMoreResult = !nextBatch.isNullOrEmpty(),
|
||||||
|
lastBatchSize = searchResult.results.orEmpty().size,
|
||||||
|
asyncSearchRequest = Success(Unit)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
currentTask?.cancel()
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.search
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import org.matrix.android.sdk.api.session.search.EventAndSender
|
||||||
|
|
||||||
|
data class SearchViewState(
|
||||||
|
// Accumulated search result
|
||||||
|
val searchResult: List<EventAndSender> = emptyList(),
|
||||||
|
val hasMoreResult: Boolean = false,
|
||||||
|
// Last batch size, will help RecyclerView to position itself
|
||||||
|
val lastBatchSize: Int = 0,
|
||||||
|
val searchTerm: String? = null,
|
||||||
|
val roomId: String = "",
|
||||||
|
// Current pagination request
|
||||||
|
val asyncSearchRequest: Async<Unit> = Uninitialized
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
constructor(args: SearchArgs) : this(roomId = args.roomId)
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ class RoomListNameFilter @Inject constructor() : Predicate<RoomSummary> {
|
||||||
var filter: String = ""
|
var filter: String = ""
|
||||||
|
|
||||||
override fun test(roomSummary: RoomSummary): Boolean {
|
override fun test(roomSummary: RoomSummary): Boolean {
|
||||||
if (filter.isBlank()) {
|
if (filter.isEmpty()) {
|
||||||
// No filter
|
// No filter
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,8 @@ import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.app.features.debug.DebugMenuActivity
|
import im.vector.app.features.debug.DebugMenuActivity
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
||||||
|
import im.vector.app.features.home.room.detail.search.SearchActivity
|
||||||
|
import im.vector.app.features.home.room.detail.search.SearchArgs
|
||||||
import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
|
import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
|
||||||
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
|
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
|
||||||
import im.vector.app.features.invite.InviteUsersToRoomActivity
|
import im.vector.app.features.invite.InviteUsersToRoomActivity
|
||||||
|
@ -329,6 +331,11 @@ class DefaultNavigator @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openSearch(context: Context, roomId: String) {
|
||||||
|
val intent = SearchActivity.newIntent(context, SearchArgs(roomId))
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
|
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
|
||||||
if (buildTask) {
|
if (buildTask) {
|
||||||
val stackBuilder = TaskStackBuilder.create(context)
|
val stackBuilder = TaskStackBuilder.create(context)
|
||||||
|
|
|
@ -109,4 +109,6 @@ interface Navigator {
|
||||||
view: View,
|
view: View,
|
||||||
inMemory: List<AttachmentData> = emptyList(),
|
inMemory: List<AttachmentData> = emptyList(),
|
||||||
options: ((MutableList<Pair<View, String>>) -> Unit)?)
|
options: ((MutableList<Pair<View, String>>) -> Unit)?)
|
||||||
|
|
||||||
|
fun openSearch(context: Context, roomId: String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||||
sealed class RoomBannedListMemberAction : VectorViewModelAction {
|
sealed class RoomBannedListMemberAction : VectorViewModelAction {
|
||||||
data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
|
data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
|
||||||
data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
|
data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
|
||||||
|
data class Filter(val filter: String) : RoomBannedListMemberAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.util.awaitCallback
|
||||||
import org.matrix.android.sdk.rx.rx
|
import org.matrix.android.sdk.rx.rx
|
||||||
import org.matrix.android.sdk.rx.unwrap
|
import org.matrix.android.sdk.rx.unwrap
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||||
|
@ -90,6 +91,15 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomBannedListMemberAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary)
|
is RoomBannedListMemberAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary)
|
||||||
is RoomBannedListMemberAction.UnBanUser -> unBanUser(action.roomMemberSummary)
|
is RoomBannedListMemberAction.UnBanUser -> unBanUser(action.roomMemberSummary)
|
||||||
|
is RoomBannedListMemberAction.Filter -> handleFilter(action)
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleFilter(action: RoomBannedListMemberAction.Filter) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
filter = action.filter
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
package im.vector.app.features.roomprofile.banned
|
package im.vector.app.features.roomprofile.banned
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.dividerItem
|
import im.vector.app.core.epoxy.dividerItem
|
||||||
import im.vector.app.core.epoxy.profiles.buildProfileSection
|
import im.vector.app.core.epoxy.profiles.buildProfileSection
|
||||||
|
@ -28,11 +26,15 @@ import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.ui.list.genericFooterItem
|
import im.vector.app.core.ui.list.genericFooterItem
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.roomprofile.members.RoomMemberSummaryFilter
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomBannedMemberListController @Inject constructor(
|
class RoomBannedMemberListController @Inject constructor(
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
private val roomMemberSummaryFilter: RoomMemberSummaryFilter,
|
||||||
colorProvider: ColorProvider
|
colorProvider: ColorProvider
|
||||||
) : TypedEpoxyController<RoomBannedMemberListViewState>() {
|
) : TypedEpoxyController<RoomBannedMemberListViewState>() {
|
||||||
|
|
||||||
|
@ -63,34 +65,37 @@ class RoomBannedMemberListController @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
buildProfileSection(quantityString)
|
buildProfileSection(quantityString)
|
||||||
|
|
||||||
bannedList.join(
|
roomMemberSummaryFilter.filter = data.filter
|
||||||
each = { _, roomMember ->
|
bannedList
|
||||||
val actionInProgress = data.onGoingModerationAction.contains(roomMember.userId)
|
.filter { roomMemberSummaryFilter.test(it) }
|
||||||
profileMatrixItemWithProgress {
|
.join(
|
||||||
id(roomMember.userId)
|
each = { _, roomMember ->
|
||||||
matrixItem(roomMember.toMatrixItem())
|
val actionInProgress = data.onGoingModerationAction.contains(roomMember.userId)
|
||||||
avatarRenderer(avatarRenderer)
|
profileMatrixItemWithProgress {
|
||||||
apply {
|
id(roomMember.userId)
|
||||||
if (actionInProgress) {
|
matrixItem(roomMember.toMatrixItem())
|
||||||
inProgress(true)
|
avatarRenderer(avatarRenderer)
|
||||||
editable(false)
|
apply {
|
||||||
} else {
|
if (actionInProgress) {
|
||||||
inProgress(false)
|
inProgress(true)
|
||||||
editable(true)
|
editable(false)
|
||||||
clickListener { _ ->
|
} else {
|
||||||
callback?.onUnbanClicked(roomMember)
|
inProgress(false)
|
||||||
|
editable(true)
|
||||||
|
clickListener { _ ->
|
||||||
|
callback?.onUnbanClicked(roomMember)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
between = { _, roomMemberBefore ->
|
||||||
|
dividerItem {
|
||||||
|
id("divider_${roomMemberBefore.userId}")
|
||||||
|
color(dividerColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
},
|
|
||||||
between = { _, roomMemberBefore ->
|
|
||||||
dividerItem {
|
|
||||||
id("divider_${roomMemberBefore.userId}")
|
|
||||||
color(dividerColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package im.vector.app.features.roomprofile.banned
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
@ -53,6 +55,7 @@ class RoomBannedMemberListFragment @Inject constructor(
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
roomMemberListController.callback = this
|
roomMemberListController.callback = this
|
||||||
setupToolbar(roomSettingsToolbar)
|
setupToolbar(roomSettingsToolbar)
|
||||||
|
setupSearchView()
|
||||||
recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
|
recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
|
||||||
|
|
||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
|
@ -84,6 +87,21 @@ class RoomBannedMemberListFragment @Inject constructor(
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupSearchView() {
|
||||||
|
searchViewAppBarLayout.isVisible = true
|
||||||
|
searchView.queryHint = getString(R.string.search_banned_user_hint)
|
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String): Boolean {
|
||||||
|
viewModel.handle(RoomBannedListMemberAction.Filter(newText))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { viewState ->
|
override fun invalidate() = withState(viewModel) { viewState ->
|
||||||
roomMemberListController.setData(viewState)
|
roomMemberListController.setData(viewState)
|
||||||
renderRoomSummary(viewState)
|
renderRoomSummary(viewState)
|
||||||
|
|
|
@ -27,6 +27,7 @@ data class RoomBannedMemberListViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val roomSummary: Async<RoomSummary> = Uninitialized,
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
val bannedMemberSummaries: Async<List<RoomMemberSummary>> = Uninitialized,
|
val bannedMemberSummaries: Async<List<RoomMemberSummary>> = Uninitialized,
|
||||||
|
val filter: String = "",
|
||||||
val onGoingModerationAction: List<String> = emptyList(),
|
val onGoingModerationAction: List<String> = emptyList(),
|
||||||
val canUserBan: Boolean = false
|
val canUserBan: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
|
@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class RoomMemberListAction : VectorViewModelAction {
|
sealed class RoomMemberListAction : VectorViewModelAction {
|
||||||
data class RevokeThreePidInvite(val stateKey: String) : RoomMemberListAction()
|
data class RevokeThreePidInvite(val stateKey: String) : RoomMemberListAction()
|
||||||
|
data class FilterMemberList(val searchTerm: String) : RoomMemberListAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,6 @@
|
||||||
package im.vector.app.features.roomprofile.members
|
package im.vector.app.features.roomprofile.members
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent
|
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.dividerItem
|
import im.vector.app.core.epoxy.dividerItem
|
||||||
import im.vector.app.core.epoxy.profiles.buildProfileSection
|
import im.vector.app.core.epoxy.profiles.buildProfileSection
|
||||||
|
@ -31,17 +25,24 @@ import im.vector.app.core.extensions.join
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomMemberListController @Inject constructor(
|
class RoomMemberListController @Inject constructor(
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
private val roomMemberSummaryFilter: RoomMemberSummaryFilter,
|
||||||
colorProvider: ColorProvider
|
colorProvider: ColorProvider
|
||||||
) : TypedEpoxyController<RoomMemberListViewState>() {
|
) : TypedEpoxyController<RoomMemberListViewState>() {
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
fun onRoomMemberClicked(roomMember: RoomMemberSummary)
|
fun onRoomMemberClicked(roomMember: RoomMemberSummary)
|
||||||
fun onThreePidInvites(event: Event)
|
fun onThreePidInviteClicked(event: Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
||||||
|
@ -53,17 +54,29 @@ class RoomMemberListController @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buildModels(data: RoomMemberListViewState?) {
|
override fun buildModels(data: RoomMemberListViewState?) {
|
||||||
val roomMembersByPowerLevel = data?.roomMemberSummaries?.invoke() ?: return
|
data ?: return
|
||||||
val threePidInvites = data.threePidInvites().orEmpty()
|
|
||||||
|
roomMemberSummaryFilter.filter = data.filter
|
||||||
|
|
||||||
|
val roomMembersByPowerLevel = data.roomMemberSummaries.invoke() ?: return
|
||||||
|
val threePidInvites = data.threePidInvites()
|
||||||
|
?.filter { event ->
|
||||||
|
event.content.toModel<RoomThirdPartyInviteContent>()
|
||||||
|
?.takeIf {
|
||||||
|
data.filter.isEmpty() || it.displayName.contains(data.filter, ignoreCase = true)
|
||||||
|
} != null
|
||||||
|
}
|
||||||
|
.orEmpty()
|
||||||
var threePidInvitesDone = threePidInvites.isEmpty()
|
var threePidInvitesDone = threePidInvites.isEmpty()
|
||||||
|
|
||||||
for ((powerLevelCategory, roomMemberList) in roomMembersByPowerLevel) {
|
for ((powerLevelCategory, roomMemberList) in roomMembersByPowerLevel) {
|
||||||
if (roomMemberList.isEmpty()) {
|
val filteredRoomMemberList = roomMemberList.filter { roomMemberSummaryFilter.test(it) }
|
||||||
|
if (filteredRoomMemberList.isEmpty()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (powerLevelCategory == RoomMemberListCategories.USER && !threePidInvitesDone) {
|
if (powerLevelCategory == RoomMemberListCategories.USER && !threePidInvitesDone) {
|
||||||
// If there is not regular invite, display threepid invite before the regular user
|
// If there is no regular invite, display threepid invite before the regular user
|
||||||
buildProfileSection(
|
buildProfileSection(
|
||||||
stringProvider.getString(RoomMemberListCategories.INVITE.titleRes)
|
stringProvider.getString(RoomMemberListCategories.INVITE.titleRes)
|
||||||
)
|
)
|
||||||
|
@ -75,7 +88,7 @@ class RoomMemberListController @Inject constructor(
|
||||||
buildProfileSection(
|
buildProfileSection(
|
||||||
stringProvider.getString(powerLevelCategory.titleRes)
|
stringProvider.getString(powerLevelCategory.titleRes)
|
||||||
)
|
)
|
||||||
roomMemberList.join(
|
filteredRoomMemberList.join(
|
||||||
each = { _, roomMember ->
|
each = { _, roomMember ->
|
||||||
profileMatrixItem {
|
profileMatrixItem {
|
||||||
id(roomMember.userId)
|
id(roomMember.userId)
|
||||||
|
@ -94,12 +107,13 @@ class RoomMemberListController @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (powerLevelCategory == RoomMemberListCategories.INVITE) {
|
if (powerLevelCategory == RoomMemberListCategories.INVITE && !threePidInvitesDone) {
|
||||||
// Display the threepid invite after the regular invite
|
// Display the threepid invite after the regular invite
|
||||||
dividerItem {
|
dividerItem {
|
||||||
id("divider_threepidinvites")
|
id("divider_threepidinvites")
|
||||||
color(dividerColor)
|
color(dividerColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildThreePidInvites(data)
|
buildThreePidInvites(data)
|
||||||
threePidInvitesDone = true
|
threePidInvitesDone = true
|
||||||
}
|
}
|
||||||
|
@ -128,7 +142,7 @@ class RoomMemberListController @Inject constructor(
|
||||||
avatarRenderer(avatarRenderer)
|
avatarRenderer(avatarRenderer)
|
||||||
editable(data.actionsPermissions.canRevokeThreePidInvite)
|
editable(data.actionsPermissions.canRevokeThreePidInvite)
|
||||||
clickListener { _ ->
|
clickListener { _ ->
|
||||||
callback?.onThreePidInvites(event)
|
callback?.onThreePidInviteClicked(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
@ -72,12 +74,28 @@ class RoomMemberListFragment @Inject constructor(
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
roomMemberListController.callback = this
|
roomMemberListController.callback = this
|
||||||
setupToolbar(roomSettingsToolbar)
|
setupToolbar(roomSettingsToolbar)
|
||||||
|
setupSearchView()
|
||||||
recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
|
recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
|
||||||
viewModel.selectSubscribe(this, RoomMemberListViewState::actionsPermissions) {
|
viewModel.selectSubscribe(this, RoomMemberListViewState::actionsPermissions) {
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupSearchView() {
|
||||||
|
searchViewAppBarLayout.isVisible = true
|
||||||
|
searchView.queryHint = getString(R.string.search_members_hint)
|
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String): Boolean {
|
||||||
|
viewModel.handle(RoomMemberListAction.FilterMemberList(newText))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
recyclerView.cleanup()
|
recyclerView.cleanup()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
@ -92,7 +110,7 @@ class RoomMemberListFragment @Inject constructor(
|
||||||
navigator.openRoomMemberProfile(roomMember.userId, roomId = roomProfileArgs.roomId, context = requireActivity())
|
navigator.openRoomMemberProfile(roomMember.userId, roomId = roomProfileArgs.roomId, context = requireActivity())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onThreePidInvites(event: Event) {
|
override fun onThreePidInviteClicked(event: Event) {
|
||||||
// Display a dialog to revoke invite if power level is high enough
|
// Display a dialog to revoke invite if power level is high enough
|
||||||
val content = event.content.toModel<RoomThirdPartyInviteContent>() ?: return
|
val content = event.content.toModel<RoomThirdPartyInviteContent>() ?: return
|
||||||
val stateKey = event.stateKey ?: return
|
val stateKey = event.stateKey ?: return
|
||||||
|
|
|
@ -188,6 +188,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
||||||
override fun handle(action: RoomMemberListAction) {
|
override fun handle(action: RoomMemberListAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action)
|
is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action)
|
||||||
|
is RoomMemberListAction.FilterMemberList -> handleFilterMemberList(action)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,4 +202,12 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleFilterMemberList(action: RoomMemberListAction.FilterMemberList) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
filter = action.searchTerm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ data class RoomMemberListViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val roomSummary: Async<RoomSummary> = Uninitialized,
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized,
|
val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized,
|
||||||
|
val filter: String = "",
|
||||||
val threePidInvites: Async<List<Event>> = Uninitialized,
|
val threePidInvites: Async<List<Event>> = Uninitialized,
|
||||||
val trustLevelMap: Async<Map<String, RoomEncryptionTrustLevel?>> = Uninitialized,
|
val trustLevelMap: Async<Map<String, RoomEncryptionTrustLevel?>> = Uninitialized,
|
||||||
val actionsPermissions: ActionPermissions = ActionPermissions()
|
val actionsPermissions: ActionPermissions = ActionPermissions()
|
||||||
|
|
|
@ -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.app.features.roomprofile.members
|
||||||
|
|
||||||
|
import io.reactivex.functions.Predicate
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomMemberSummaryFilter @Inject constructor() : Predicate<RoomMemberSummary> {
|
||||||
|
var filter: String = ""
|
||||||
|
|
||||||
|
override fun test(roomMemberSummary: RoomMemberSummary): Boolean {
|
||||||
|
if (filter.isEmpty()) {
|
||||||
|
// No filter
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return roomMemberSummary.displayName?.contains(filter, ignoreCase = true).orFalse()
|
||||||
|
// We should maybe exclude the domain from the userId
|
||||||
|
|| roomMemberSummary.userId.contains(filter, ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,23 +23,21 @@ import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.intent.getMimeTypeFromUri
|
import im.vector.app.core.intent.getMimeTypeFromUri
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.StringProvider
|
|
||||||
import im.vector.app.core.utils.saveMedia
|
import im.vector.app.core.utils.saveMedia
|
||||||
import im.vector.app.core.utils.shareMedia
|
import im.vector.app.core.utils.shareMedia
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
import im.vector.app.features.roomprofile.RoomProfileArgs
|
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||||
import kotlinx.android.synthetic.main.fragment_room_uploads.*
|
import kotlinx.android.synthetic.main.fragment_room_uploads.*
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomUploadsFragment @Inject constructor(
|
class RoomUploadsFragment @Inject constructor(
|
||||||
private val viewModelFactory: RoomUploadsViewModel.Factory,
|
private val viewModelFactory: RoomUploadsViewModel.Factory,
|
||||||
private val stringProvider: StringProvider,
|
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val notificationUtils: NotificationUtils
|
private val notificationUtils: NotificationUtils
|
||||||
) : VectorBaseFragment(), RoomUploadsViewModel.Factory by viewModelFactory {
|
) : VectorBaseFragment(), RoomUploadsViewModel.Factory by viewModelFactory {
|
||||||
|
@ -58,8 +56,8 @@ class RoomUploadsFragment @Inject constructor(
|
||||||
|
|
||||||
TabLayoutMediator(roomUploadsTabs, roomUploadsViewPager) { tab, position ->
|
TabLayoutMediator(roomUploadsTabs, roomUploadsViewPager) { tab, position ->
|
||||||
when (position) {
|
when (position) {
|
||||||
0 -> tab.text = stringProvider.getString(R.string.uploads_media_title)
|
0 -> tab.text = getString(R.string.uploads_media_title)
|
||||||
1 -> tab.text = stringProvider.getString(R.string.uploads_files_title)
|
1 -> tab.text = getString(R.string.uploads_files_title)
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
|
@ -70,7 +68,7 @@ class RoomUploadsFragment @Inject constructor(
|
||||||
is RoomUploadsViewEvents.FileReadyForSharing -> {
|
is RoomUploadsViewEvents.FileReadyForSharing -> {
|
||||||
shareMedia(requireContext(), it.file, getMimeTypeFromUri(requireContext(), it.file.toUri()))
|
shareMedia(requireContext(), it.file, getMimeTypeFromUri(requireContext(), it.file.toUri()))
|
||||||
}
|
}
|
||||||
is RoomUploadsViewEvents.FileReadyForSaving -> {
|
is RoomUploadsViewEvents.FileReadyForSaving -> {
|
||||||
saveMedia(
|
saveMedia(
|
||||||
context = requireContext(),
|
context = requireContext(),
|
||||||
file = it.file,
|
file = it.file,
|
||||||
|
@ -79,7 +77,7 @@ class RoomUploadsFragment @Inject constructor(
|
||||||
notificationUtils = notificationUtils
|
notificationUtils = notificationUtils
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is RoomUploadsViewEvents.Failure -> showFailure(it.throwable)
|
is RoomUploadsViewEvents.Failure -> showFailure(it.throwable)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape
|
||||||
|
android:innerRadius="0dp"
|
||||||
|
android:shape="ring"
|
||||||
|
android:thicknessRatio="2"
|
||||||
|
android:useLevel="false">
|
||||||
|
<solid android:color="@android:color/transparent" />
|
||||||
|
<stroke android:width="2dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:bottom="14dp"
|
||||||
|
android:drawable="@drawable/ic_search"
|
||||||
|
android:left="14dp"
|
||||||
|
android:right="14dp"
|
||||||
|
android:top="14dp" />
|
||||||
|
</layer-list>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/searchToolbar"
|
||||||
|
style="@style/VectorToolbarStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:contentInsetStart="0dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SearchView
|
||||||
|
android:id="@+id/searchView"
|
||||||
|
style="@style/VectorSearchView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@color/base_color"
|
||||||
|
app:queryHint="@string/search_hint" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/searchFragmentContainer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/searchToolbar" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -53,16 +53,53 @@
|
||||||
|
|
||||||
</androidx.appcompat.widget.Toolbar>
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/recyclerView"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:overScrollMode="always"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/roomSettingsToolbar"
|
app:layout_constraintTop_toBottomOf="@+id/roomSettingsToolbar">
|
||||||
tools:listitem="@layout/item_profile_action" />
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:overScrollMode="always"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
tools:listitem="@layout/item_profile_action" />
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/searchViewAppBarLayout"
|
||||||
|
style="@style/VectorAppBarLayoutStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<!-- Use an extra container for the margin to be taken into account by the layout_scrollFlags -->
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways|snap">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SearchView
|
||||||
|
android:id="@+id/searchView"
|
||||||
|
style="@style/VectorSearchView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
tools:queryHint="@string/search_hint" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
<include layout="@layout/merge_overlay_waiting_view" />
|
<include layout="@layout/merge_overlay_waiting_view" />
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<im.vector.app.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/stateView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/searchResultRecycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:overScrollMode="always"
|
||||||
|
tools:listitem="@layout/item_search_result" />
|
||||||
|
|
||||||
|
</im.vector.app.core.platform.StateView>
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?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:padding="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/messageAvatarImageView"
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageMemberNameView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/messageTimeView"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/messageAvatarImageView"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@sample/matrix.json/data/displayName" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageTimeView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignBaseline="@+id/messageMemberNameView"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/messageMemberNameView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:text="@tools:sample/date/hhmm" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageContentView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/messageMemberNameView"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/messageMemberNameView"
|
||||||
|
tools:text="@sample/matrix.json/data/message" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -3,6 +3,11 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/search"
|
||||||
|
android:title="@string/search"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/video_call"
|
android:id="@+id/video_call"
|
||||||
android:icon="@drawable/ic_video"
|
android:icon="@drawable/ic_video"
|
||||||
|
|
|
@ -623,11 +623,13 @@
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<string name="search_hint">Search</string>
|
<string name="search_hint">Search</string>
|
||||||
<string name="search_members_hint">Filter room members</string>
|
<string name="search_members_hint">Filter room members</string>
|
||||||
|
<string name="search_banned_user_hint">Filter banned users</string>
|
||||||
<string name="search_no_results">No results</string>
|
<string name="search_no_results">No results</string>
|
||||||
<string name="tab_title_search_rooms">ROOMS</string>
|
<string name="tab_title_search_rooms">ROOMS</string>
|
||||||
<string name="tab_title_search_messages">MESSAGES</string>
|
<string name="tab_title_search_messages">MESSAGES</string>
|
||||||
<string name="tab_title_search_people">PEOPLE</string>
|
<string name="tab_title_search_people">PEOPLE</string>
|
||||||
<string name="tab_title_search_files">FILES</string>
|
<string name="tab_title_search_files">FILES</string>
|
||||||
|
<string name="search_is_not_supported_in_e2e_room">Searching in encrypted rooms is not supported yet.</string>
|
||||||
|
|
||||||
<!-- Room recents -->
|
<!-- Room recents -->
|
||||||
<string name="room_recents_join">JOIN</string>
|
<string name="room_recents_join">JOIN</string>
|
||||||
|
@ -1850,11 +1852,11 @@
|
||||||
<string name="block_user">"IGNORE USER"</string>
|
<string name="block_user">"IGNORE USER"</string>
|
||||||
|
|
||||||
<string name="content_reported_title">"Content reported"</string>
|
<string name="content_reported_title">"Content reported"</string>
|
||||||
<string name="content_reported_content">"This content was reported.\n\nIf you don't want to see any more content from this user, you can block him to hide his messages"</string>
|
<string name="content_reported_content">"This content was reported.\n\nIf you don't want to see any more content from this user, you can ignore them to hide their messages."</string>
|
||||||
<string name="content_reported_as_spam_title">"Reported as spam"</string>
|
<string name="content_reported_as_spam_title">"Reported as spam"</string>
|
||||||
<string name="content_reported_as_spam_content">"This content was reported as spam.\n\nIf you don't want to see any more content from this user, you can block him to hide his messages"</string>
|
<string name="content_reported_as_spam_content">"This content was reported as spam.\n\nIf you don't want to see any more content from this user, you can ignore them to hide their messages."</string>
|
||||||
<string name="content_reported_as_inappropriate_title">"Reported as inappropriate"</string>
|
<string name="content_reported_as_inappropriate_title">"Reported as inappropriate"</string>
|
||||||
<string name="content_reported_as_inappropriate_content">"This content was reported as inappropriate.\n\nIf you don't want to see any more content from this user, you can block him to hide his messages"</string>
|
<string name="content_reported_as_inappropriate_content">"This content was reported as inappropriate.\n\nIf you don't want to see any more content from this user, you can ignore them to hide their messages."</string>
|
||||||
|
|
||||||
<string name="permissions_rationale_msg_keys_backup_export">Element needs permission to save your E2E keys on disk.\n\nPlease allow access on the next pop-up to be able to export your keys manually.</string>
|
<string name="permissions_rationale_msg_keys_backup_export">Element needs permission to save your E2E keys on disk.\n\nPlease allow access on the next pop-up to be able to export your keys manually.</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue