Uploads: add screen - WIP

This commit is contained in:
Benoit Marty 2020-05-19 01:39:04 +02:00
parent 8a9498bae4
commit 0992e76800
12 changed files with 491 additions and 2 deletions

View File

@ -282,6 +282,7 @@ dependencies {
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0"
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
// Log

View File

@ -76,6 +76,9 @@ import im.vector.riotx.features.roommemberprofile.devices.DeviceTrustInfoActionF
import im.vector.riotx.features.roomprofile.RoomProfileFragment
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsFilesFragment
import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsMediaFragment
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
import im.vector.riotx.features.settings.VectorSettingsLabsFragment
@ -96,9 +99,9 @@ import im.vector.riotx.features.settings.locale.LocalePickerFragment
import im.vector.riotx.features.settings.push.PushGatewaysFragment
import im.vector.riotx.features.share.IncomingShareFragment
import im.vector.riotx.features.signout.soft.SoftLogoutFragment
import im.vector.riotx.features.terms.ReviewTermsFragment
import im.vector.riotx.features.userdirectory.KnownUsersFragment
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
import im.vector.riotx.features.terms.ReviewTermsFragment
@Module
interface FragmentModule {
@ -308,6 +311,21 @@ interface FragmentModule {
@FragmentKey(RoomMemberListFragment::class)
fun bindRoomMemberListFragment(fragment: RoomMemberListFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomUploadsFragment::class)
fun bindRoomUploadsFragment(fragment: RoomUploadsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomUploadsMediaFragment::class)
fun bindRoomUploadsMediaFragment(fragment: RoomUploadsMediaFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomUploadsFilesFragment::class)
fun bindRoomUploadsFilesFragment(fragment: RoomUploadsFilesFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomSettingsFragment::class)

View File

@ -27,6 +27,7 @@ import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
@ -66,7 +67,7 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
}
private fun openRoomUploads() {
notImplemented("Open room uploads")
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomUploadsFragment::class.java, roomProfileArgs)
}
private fun openRoomSettings() {

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.uploads
import im.vector.riotx.core.platform.VectorViewModelAction
sealed class RoomUploadsAction : VectorViewModelAction

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.uploads
import android.os.Bundle
import android.view.View
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.roomprofile.RoomProfileArgs
import kotlinx.android.synthetic.main.fragment_room_uploads.*
import javax.inject.Inject
class RoomUploadsFragment @Inject constructor(
private val viewModelFactory: RoomUploadsViewModel.Factory,
private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment(), RoomUploadsViewModel.Factory by viewModelFactory {
private val roomProfileArgs: RoomProfileArgs by args()
private val viewModel: RoomUploadsViewModel by fragmentViewModel()
override fun getLayoutResId() = R.layout.fragment_room_uploads
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val sectionsPagerAdapter = RoomUploadsPagerAdapter(childFragmentManager, stringProvider)
view_pager.adapter = sectionsPagerAdapter
tabs.setupWithViewPager(view_pager)
setupToolbar(matrixProfileToolbar)
// Initialize your view, subscribe to viewModel...
}
/*
override fun onDestroyView() {
super.onDestroyView()
// Clear your view, unsubscribe...
}
*/
override fun invalidate() = withState(viewModel) { state ->
renderRoomSummary(state)
}
private fun renderRoomSummary(state: RoomUploadsViewState) {
state.roomSummary()?.let {
matrixProfileToolbarTitleView.text = it.displayName
avatarRenderer.render(it.toMatrixItem(), matrixProfileToolbarAvatarImageView)
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.uploads
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsFilesFragment
import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsMediaFragment
private val TAB_TITLES = arrayOf(
R.string.uploads_title_media,
R.string.uploads_title_files
)
/**
* A [FragmentPagerAdapter] that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
class RoomUploadsPagerAdapter(
fm: FragmentManager,
private val stringProvider: StringProvider
) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): Fragment {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class below).
return if (position == 0) {
RoomUploadsMediaFragment()
} else {
RoomUploadsFilesFragment()
}
}
override fun getPageTitle(position: Int): CharSequence? {
return stringProvider.getString(TAB_TITLES[position])
}
override fun getCount(): Int {
// Show 2 total pages.
return 2
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.uploads
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult
import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel
import kotlinx.coroutines.launch
class RoomUploadsViewModel @AssistedInject constructor(
@Assisted initialState: RoomUploadsViewState,
private val session: Session
) : VectorViewModel<RoomUploadsViewState, RoomUploadsAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: RoomUploadsViewState): RoomUploadsViewModel
}
companion object : MvRxViewModelFactory<RoomUploadsViewModel, RoomUploadsViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomUploadsViewState): RoomUploadsViewModel? {
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}
private val room = session.getRoom(initialState.roomId)!!
init {
observeRoomSummary()
// Send a first request
handleLoadMore()
}
private fun observeRoomSummary() {
room.rx().liveRoomSummary()
.unwrap()
.execute { async ->
copy(roomSummary = async)
}
}
private fun handleLoadMore() = withState { state ->
if (state.asyncEventsRequest is Loading) return@withState
setState {
copy(
asyncEventsRequest = Loading()
)
}
viewModelScope.launch {
try {
val result = awaitCallback<GetUploadsResult> {
room.getUploads(20, token, it)
}
token = result.nextToken
setState {
copy(
asyncEventsRequest = Uninitialized,
events = this.events + result.events,
hasMore = result.nextToken != null
)
}
} catch (failure: Throwable) {
// TODO Post fail
setState {
copy(
asyncEventsRequest = Fail(failure)
)
}
}
}
}
private var token: String? = null
override fun handle(action: RoomUploadsAction) {
// when (action) {
//
// }.exhaustive
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.uploads
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.features.roomprofile.RoomProfileArgs
data class RoomUploadsViewState(
val roomId: String = "",
val roomSummary: Async<RoomSummary> = Uninitialized,
// Store cumul of pagination result, grouped by type
val mediaEvents: List<Event> = emptyList(),
val fileEvents: List<Event> = emptyList(),
// Current pagination request
val asyncEventsRequest: Async<List<Event>> = Uninitialized,
// True if more result are available server side
val hasMore: Boolean = false
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.uploads.child
import com.airbnb.mvrx.parentFragmentViewModel
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel
import javax.inject.Inject
/**
* A placeholder fragment containing a simple view.
*/
class RoomUploadsFilesFragment @Inject constructor() : VectorBaseFragment() {
private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class)
override fun getLayoutResId() = R.layout.fragment_generic_recycler
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.uploads.child
import com.airbnb.mvrx.parentFragmentViewModel
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel
import javax.inject.Inject
/**
* A placeholder fragment containing a simple view.
*/
class RoomUploadsMediaFragment @Inject constructor() : VectorBaseFragment() {
private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class)
override fun getLayoutResId() = R.layout.fragment_generic_recycler
}

View File

@ -0,0 +1,85 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
style="@style/VectorAppBarLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp">
<androidx.appcompat.widget.Toolbar
android:id="@+id/matrixProfileToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:elevation="4dp"
app:layout_collapseMode="pin">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/matrixProfileToolbarContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/matrixProfileToolbarAvatarImageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<ImageView
android:id="@+id/matrixProfileDecorationToolbarAvatarImageView"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintCircle="@+id/matrixProfileToolbarAvatarImageView"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="20dp"
tools:ignore="MissingConstraints"
tools:src="@drawable/ic_shield_trusted" />
<im.vector.riotx.core.platform.EllipsizingTextView
android:id="@+id/matrixProfileToolbarTitleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_toolbar_primary_text_color"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/matrixProfileToolbarAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="@sample/matrix.json/data/roomName" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/matrixProfileToolbarAvatarImageView"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1788,6 +1788,11 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="attachment_type_sticker">"Sticker"</string>
<string name="error_handling_incoming_share">Couldn\'t handle share data</string>
<string name="uploads_media_title">MEDIA</string>
<string name="uploads_media_no_result">There is no media in this room</string>
<string name="uploads_files_title">FILES</string>
<string name="uploads_files_no_result">There is no files in this room</string>
<string name="report_content_spam">"It's spam"</string>
<string name="report_content_inappropriate">"It's inappropriate"</string>
<string name="report_content_custom">"Custom report…"</string>