ganfra
2 years ago
committed by
GitHub
35 changed files with 629 additions and 185 deletions
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* Copyright (c) 2023 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 io.element.android.x.features.roomlist |
||||
|
||||
import app.cash.molecule.RecompositionClock |
||||
import app.cash.molecule.moleculeFlow |
||||
import app.cash.turbine.test |
||||
import com.google.common.truth.Truth.assertThat |
||||
import io.element.android.x.libraries.matrixtest.FakeMatrixClient |
||||
import io.element.android.x.matrix.core.SessionId |
||||
import kotlinx.coroutines.test.runTest |
||||
import org.junit.Test |
||||
|
||||
class RoomListPresenterTests { |
||||
|
||||
@Test |
||||
fun `present - should start with no user and then load user with success`() = runTest { |
||||
|
||||
val presenter = RoomListPresenter( |
||||
FakeMatrixClient( |
||||
SessionId("sessionId") |
||||
), LastMessageFormatter()) |
||||
moleculeFlow(RecompositionClock.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
val initialState = awaitItem() |
||||
assertThat(initialState.matrixUser).isNull() |
||||
val withUserState = awaitItem() |
||||
assertThat(withUserState).isNotNull() |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* Copyright (c) 2023 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 io.element.android.x.matrix.auth |
||||
|
||||
import io.element.android.x.matrix.MatrixClient |
||||
import io.element.android.x.matrix.core.SessionId |
||||
import kotlinx.coroutines.flow.Flow |
||||
|
||||
interface MatrixAuthenticationService { |
||||
fun isLoggedIn(): Flow<Boolean> |
||||
suspend fun getLatestSessionId(): SessionId? |
||||
suspend fun restoreSession(): MatrixClient? |
||||
fun getHomeserver(): String? |
||||
fun getHomeserverOrDefault(): String |
||||
suspend fun setHomeserver(homeserver: String) |
||||
suspend fun login(username: String, password: String): SessionId |
||||
} |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
/* |
||||
* Copyright (c) 2023 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 io.element.android.x.matrix.di |
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo |
||||
import dagger.Module |
||||
import dagger.Provides |
||||
import io.element.android.x.di.AppScope |
||||
import io.element.android.x.di.SingleIn |
||||
import org.matrix.rustcomponents.sdk.AuthenticationService |
||||
import java.io.File |
||||
|
||||
@Module |
||||
@ContributesTo(AppScope::class) |
||||
object MatrixModule { |
||||
|
||||
@Provides |
||||
@SingleIn(AppScope::class) |
||||
fun providesRustAuthenticationService(baseDirectory: File): AuthenticationService { |
||||
return AuthenticationService(baseDirectory.absolutePath) |
||||
} |
||||
} |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
/* |
||||
* Copyright (c) 2023 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 io.element.android.x.matrix.media |
||||
|
||||
import io.element.android.x.matrix.MatrixClient |
||||
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl |
||||
|
||||
internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver { |
||||
|
||||
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? { |
||||
if (url.isNullOrEmpty()) return null |
||||
val mediaSource = mediaSourceFromUrl(url) |
||||
return resolve(MediaResolver.Meta(mediaSource, kind)) |
||||
} |
||||
|
||||
override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? { |
||||
return when (meta.kind) { |
||||
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(meta.source) |
||||
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource( |
||||
meta.source, |
||||
meta.kind.width.toLong(), |
||||
meta.kind.height.toLong() |
||||
) |
||||
}.getOrNull() |
||||
} |
||||
} |
@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
|
||||
/* |
||||
* Copyright (c) 2022 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 io.element.android.x.matrix.session |
||||
|
||||
import android.content.Context |
||||
import androidx.datastore.core.DataStore |
||||
import androidx.datastore.preferences.core.Preferences |
||||
import androidx.datastore.preferences.core.edit |
||||
import androidx.datastore.preferences.core.stringPreferencesKey |
||||
import androidx.datastore.preferences.preferencesDataStore |
||||
import com.squareup.anvil.annotations.ContributesBinding |
||||
import io.element.android.x.di.AppScope |
||||
import io.element.android.x.di.ApplicationContext |
||||
import io.element.android.x.di.SingleIn |
||||
import kotlinx.coroutines.flow.Flow |
||||
import kotlinx.coroutines.flow.firstOrNull |
||||
import kotlinx.coroutines.flow.map |
||||
import kotlinx.serialization.Serializable |
||||
import kotlinx.serialization.decodeFromString |
||||
import kotlinx.serialization.encodeToString |
||||
import kotlinx.serialization.json.Json |
||||
import org.matrix.rustcomponents.sdk.Session |
||||
import javax.inject.Inject |
||||
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_sessions") |
||||
|
||||
// TODO It contains the access token, so it has to be stored in a more secured storage. |
||||
private val sessionKey = stringPreferencesKey("session") |
||||
|
||||
@SingleIn(AppScope::class) |
||||
@ContributesBinding(AppScope::class) |
||||
class PreferencesSessionStore @Inject constructor( |
||||
@ApplicationContext context: Context |
||||
) : SessionStore { |
||||
@Serializable |
||||
data class SessionData( |
||||
val accessToken: String, |
||||
val deviceId: String, |
||||
val homeserverUrl: String, |
||||
val isSoftLogout: Boolean, |
||||
val refreshToken: String?, |
||||
val userId: String |
||||
) |
||||
|
||||
private val store = context.dataStore |
||||
|
||||
override fun isLoggedIn(): Flow<Boolean> { |
||||
return store.data.map { prefs -> |
||||
prefs[sessionKey] != null |
||||
} |
||||
} |
||||
|
||||
override suspend fun storeData(session: Session) { |
||||
store.edit { prefs -> |
||||
val sessionData = SessionData( |
||||
accessToken = session.accessToken, |
||||
deviceId = session.deviceId, |
||||
homeserverUrl = session.homeserverUrl, |
||||
isSoftLogout = session.isSoftLogout, |
||||
refreshToken = session.refreshToken, |
||||
userId = session.userId |
||||
) |
||||
val encodedSession = Json.encodeToString(sessionData) |
||||
prefs[sessionKey] = encodedSession |
||||
} |
||||
} |
||||
|
||||
override suspend fun getLatestSession(): Session? { |
||||
return store.data.firstOrNull()?.let { prefs -> |
||||
val encodedSession = prefs[sessionKey] ?: return@let null |
||||
val sessionData = Json.decodeFromString<SessionData>(encodedSession) |
||||
Session( |
||||
accessToken = sessionData.accessToken, |
||||
deviceId = sessionData.deviceId, |
||||
homeserverUrl = sessionData.homeserverUrl, |
||||
isSoftLogout = sessionData.isSoftLogout, |
||||
refreshToken = sessionData.refreshToken, |
||||
userId = sessionData.userId |
||||
) |
||||
} |
||||
} |
||||
|
||||
override suspend fun reset() { |
||||
store.edit { it.clear() } |
||||
} |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
/* |
||||
* Copyright (c) 2023 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. |
||||
*/ |
||||
|
||||
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed |
||||
@Suppress("DSL_SCOPE_VIOLATION") |
||||
plugins { |
||||
id("io.element.android-library") |
||||
} |
||||
|
||||
android { |
||||
namespace = "io.element.android.x.libraries.matrix.test" |
||||
} |
||||
|
||||
dependencies { |
||||
api(project(":libraries:matrix")) |
||||
api(libs.coroutines.core) |
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<!-- |
||||
~ Copyright (c) 2023 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. |
||||
--> |
||||
|
||||
<manifest/> |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
/* |
||||
* Copyright (c) 2023 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 io.element.android.x.libraries.matrixtest |
||||
|
||||
import io.element.android.x.matrix.MatrixClient |
||||
import io.element.android.x.matrix.core.RoomId |
||||
import io.element.android.x.matrix.core.SessionId |
||||
import io.element.android.x.matrix.core.UserId |
||||
import io.element.android.x.libraries.matrixtest.media.FakeMediaResolver |
||||
import io.element.android.x.matrix.media.MediaResolver |
||||
import io.element.android.x.libraries.matrixtest.room.FakeMatrixRoom |
||||
import io.element.android.x.libraries.matrixtest.room.InMemoryRoomSummaryDataSource |
||||
import io.element.android.x.matrix.room.MatrixRoom |
||||
import io.element.android.x.matrix.room.RoomSummaryDataSource |
||||
import org.matrix.rustcomponents.sdk.MediaSource |
||||
|
||||
class FakeMatrixClient(override val sessionId: SessionId) : MatrixClient { |
||||
|
||||
override fun getRoom(roomId: RoomId): MatrixRoom? { |
||||
return FakeMatrixRoom(roomId) |
||||
} |
||||
|
||||
override fun startSync() = Unit |
||||
|
||||
override fun stopSync() = Unit |
||||
|
||||
override fun roomSummaryDataSource(): RoomSummaryDataSource { |
||||
return InMemoryRoomSummaryDataSource() |
||||
} |
||||
|
||||
override fun mediaResolver(): MediaResolver { |
||||
return FakeMediaResolver() |
||||
} |
||||
|
||||
override suspend fun logout() = Unit |
||||
|
||||
override fun userId(): UserId = UserId("") |
||||
|
||||
override suspend fun loadUserDisplayName(): Result<String> { |
||||
return Result.success("") |
||||
} |
||||
|
||||
override suspend fun loadUserAvatarURLString(): Result<String> { |
||||
return Result.success("") |
||||
} |
||||
|
||||
override suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray> { |
||||
return Result.success(ByteArray(0)) |
||||
} |
||||
|
||||
override suspend fun loadMediaThumbnailForSource(source: MediaSource, width: Long, height: Long): Result<ByteArray> { |
||||
return Result.success(ByteArray(0)) |
||||
} |
||||
|
||||
override fun close() = Unit |
||||
} |
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
/* |
||||
* Copyright (c) 2023 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 io.element.android.x.libraries.matrixtest.room |
||||
|
||||
import io.element.android.x.matrix.core.EventId |
||||
import io.element.android.x.matrix.core.RoomId |
||||
import io.element.android.x.matrix.room.MatrixRoom |
||||
import io.element.android.x.libraries.matrixtest.timeline.FakeMatrixTimeline |
||||
import io.element.android.x.matrix.timeline.MatrixTimeline |
||||
import kotlinx.coroutines.flow.Flow |
||||
import kotlinx.coroutines.flow.emptyFlow |
||||
|
||||
class FakeMatrixRoom( |
||||
override val roomId: RoomId, |
||||
override val name: String? = null, |
||||
override val bestName: String = "", |
||||
override val displayName: String = "", |
||||
override val topic: String? = null, |
||||
override val avatarUrl: String? = null |
||||
) : MatrixRoom { |
||||
|
||||
override fun syncUpdateFlow(): Flow<Long> { |
||||
return emptyFlow() |
||||
} |
||||
|
||||
override fun timeline(): MatrixTimeline { |
||||
return FakeMatrixTimeline() |
||||
} |
||||
|
||||
override suspend fun userDisplayName(userId: String): Result<String?> { |
||||
return Result.success("") |
||||
} |
||||
|
||||
override suspend fun userAvatarUrl(userId: String): Result<String?> { |
||||
TODO("Not yet implemented") |
||||
} |
||||
|
||||
override suspend fun sendMessage(message: String): Result<Unit> { |
||||
TODO("Not yet implemented") |
||||
} |
||||
|
||||
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> { |
||||
TODO("Not yet implemented") |
||||
} |
||||
|
||||
override suspend fun replyMessage(eventId: EventId, message: String): Result<Unit> { |
||||
TODO("Not yet implemented") |
||||
} |
||||
|
||||
override suspend fun redactEvent(eventId: EventId, reason: String?): Result<Unit> { |
||||
TODO("Not yet implemented") |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* Copyright (c) 2023 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 io.element.android.x.libraries.matrixtest.room |
||||
|
||||
import io.element.android.x.matrix.room.RoomSummary |
||||
import io.element.android.x.matrix.room.RoomSummaryDataSource |
||||
import kotlinx.coroutines.flow.Flow |
||||
import kotlinx.coroutines.flow.emptyFlow |
||||
|
||||
class InMemoryRoomSummaryDataSource : RoomSummaryDataSource { |
||||
|
||||
override fun roomSummaries(): Flow<List<RoomSummary>> { |
||||
return emptyFlow() |
||||
} |
||||
|
||||
override fun setSlidingSyncRange(range: IntRange) = Unit |
||||
} |
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
/* |
||||
* Copyright (c) 2023 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 io.element.android.x.libraries.matrixtest.timeline |
||||
|
||||
import io.element.android.x.matrix.core.EventId |
||||
import io.element.android.x.matrix.timeline.MatrixTimeline |
||||
import io.element.android.x.matrix.timeline.MatrixTimelineItem |
||||
import kotlinx.coroutines.flow.Flow |
||||
import kotlinx.coroutines.flow.emptyFlow |
||||
import org.matrix.rustcomponents.sdk.TimelineListener |
||||
|
||||
class FakeMatrixTimeline : MatrixTimeline { |
||||
|
||||
override var callback: MatrixTimeline.Callback? |
||||
get() = null |
||||
set(value) {} |
||||
|
||||
override val hasMoreToLoad: Boolean |
||||
get() = true |
||||
|
||||
override fun timelineItems(): Flow<List<MatrixTimelineItem>> { |
||||
return emptyFlow() |
||||
} |
||||
|
||||
override suspend fun paginateBackwards(count: Int): Result<Unit> { |
||||
return Result.success(Unit) |
||||
} |
||||
|
||||
override fun addListener(timelineListener: TimelineListener) = Unit |
||||
|
||||
override fun initialize() = Unit |
||||
|
||||
override fun dispose() = Unit |
||||
|
||||
override suspend fun sendMessage(message: String): Result<Unit> { |
||||
return Result.success(Unit) |
||||
} |
||||
|
||||
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> { |
||||
return Result.success(Unit) |
||||
} |
||||
|
||||
override suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result<Unit> { |
||||
return Result.success(Unit) |
||||
} |
||||
} |
Loading…
Reference in new issue