Merge pull request #2285 from vector-im/feature/bma/uCrop

Feature/bma/u crop
This commit is contained in:
Benoit Marty 2020-10-28 16:54:27 +01:00 committed by GitHub
commit f127a75e38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 197 additions and 152 deletions

View File

@ -87,6 +87,7 @@ import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
import im.vector.app.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
import im.vector.app.features.settings.VectorSettingsGeneralFragment
import im.vector.app.features.settings.VectorSettingsHelpAboutFragment
import im.vector.app.features.settings.VectorSettingsLabsFragment
import im.vector.app.features.settings.VectorSettingsNotificationPreferenceFragment
@ -292,6 +293,11 @@ interface FragmentModule {
@FragmentKey(VectorSettingsPinFragment::class)
fun bindVectorSettingsPinFragment(fragment: VectorSettingsPinFragment): Fragment
@Binds
@IntoMap
@FragmentKey(VectorSettingsGeneralFragment::class)
fun bindVectorSettingsGeneralFragment(fragment: VectorSettingsGeneralFragment): Fragment
@Binds
@IntoMap
@FragmentKey(PushRulesFragment::class)

View File

@ -19,25 +19,39 @@ package im.vector.app.core.dialogs
import android.app.Activity
import android.net.Uri
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import com.yalantis.ucrop.UCrop
import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.media.createUCropWithDefaultSettings
import im.vector.lib.multipicker.MultiPicker
import im.vector.lib.multipicker.entity.MultiPickerImageType
import java.io.File
/**
* Use to let the user choose between Camera (with permission handling) and Gallery (with single image selection),
* then edit the image
* [Listener.onImageReady] will be called with an uri of a square image store in the cache of the application.
* It's up to the caller to delete the file.
*/
class GalleryOrCameraDialogHelper(
private val fragment: Fragment
// must implement GalleryOrCameraDialogHelper.Listener
private val fragment: Fragment,
private val colorProvider: ColorProvider
) {
interface Listener {
fun onImageReady(image: MultiPickerImageType)
fun onImageReady(uri: Uri?)
}
private val activity by lazy { fragment.requireActivity() }
private val activity
get() = fragment.requireActivity()
private val listener: Listener = fragment as? Listener ?: error("Fragment must implements GalleryOrCameraDialogHelper.Listener")
private val listener = fragment as? Listener ?: error("Fragment must implement GalleryOrCameraDialogHelper.Listener")
private val takePhotoPermissionActivityResultLauncher = fragment.registerForPermissionsResult { allGranted ->
if (allGranted) {
@ -49,8 +63,8 @@ class GalleryOrCameraDialogHelper(
if (activityResult.resultCode == Activity.RESULT_OK) {
avatarCameraUri?.let { uri ->
MultiPicker.get(MultiPicker.CAMERA)
.getTakenPhoto(fragment.requireContext(), uri)
?.let { listener.onImageReady(it) }
.getTakenPhoto(activity, uri)
?.let { startUCrop(it) }
}
}
}
@ -59,37 +73,53 @@ class GalleryOrCameraDialogHelper(
if (activityResult.resultCode == Activity.RESULT_OK) {
MultiPicker
.get(MultiPicker.IMAGE)
.getSelectedFiles(fragment.requireContext(), activityResult.data)
.getSelectedFiles(activity, activityResult.data)
.firstOrNull()
?.let { listener.onImageReady(it) }
?.let { startUCrop(it) }
}
}
private val uCropActivityResultLauncher = fragment.registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
activityResult.data?.let { listener.onImageReady(UCrop.getOutput(it)) }
}
}
private fun startUCrop(image: MultiPickerImageType) {
val destinationFile = File(activity.cacheDir, "${image.displayName}_e_${System.currentTimeMillis()}")
val uri = image.contentUri
createUCropWithDefaultSettings(colorProvider, uri, destinationFile.toUri(), fragment.getString(R.string.rotate_and_crop_screen_title))
.withAspectRatio(1f, 1f)
.getIntent(activity)
.let { uCropActivityResultLauncher.launch(it) }
}
private enum class Type {
Gallery,
Camera
Camera,
Gallery
}
fun show() {
AlertDialog.Builder(fragment.requireContext())
AlertDialog.Builder(activity)
.setTitle(R.string.attachment_type_dialog_title)
.setItems(arrayOf(
fragment.getString(R.string.attachment_type_camera),
fragment.getString(R.string.attachment_type_gallery)
)) { dialog, which ->
dialog.cancel()
)) { _, which ->
onAvatarTypeSelected(if (which == 0) Type.Camera else Type.Gallery)
}
.setPositiveButton(R.string.cancel, null)
.show()
}
private fun onAvatarTypeSelected(type: Type) {
when (type) {
Type.Gallery ->
MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
Type.Camera ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, activity, takePhotoPermissionActivityResultLauncher)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(fragment.requireContext(), takePhotoActivityResultLauncher)
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(activity, takePhotoActivityResultLauncher)
}
Type.Gallery ->
MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
}
}

View File

@ -19,7 +19,6 @@ package im.vector.app.features.attachments.preview
import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.Menu
@ -39,6 +38,7 @@ import com.airbnb.mvrx.withState
import com.yalantis.ucrop.UCrop
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.OnSnapPositionChangeListener
@ -49,7 +49,6 @@ import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_attachments_preview.*
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import timber.log.Timber
import java.io.File
import javax.inject.Inject
@ -80,20 +79,15 @@ class AttachmentsPreviewFragment @Inject constructor(
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
if (requestCode == UCrop.REQUEST_CROP && data != null) {
Timber.v("Crop success")
handleCropResult(data)
private val uCropActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == RESULT_OK) {
val resultUri = activityResult.data?.let { UCrop.getOutput(it) }
if (resultUri != null) {
viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultUri))
} else {
Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show()
}
}
if (resultCode == UCrop.RESULT_ERROR) {
Timber.v("Crop error")
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -170,15 +164,6 @@ class AttachmentsPreviewFragment @Inject constructor(
}
}
private fun handleCropResult(result: Intent) {
val resultUri = UCrop.getOutput(result)
if (resultUri != null) {
viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultUri))
} else {
Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show()
}
}
private fun handleRemoveAction() {
viewModel.handle(AttachmentsPreviewAction.RemoveCurrentAttachment)
}
@ -187,8 +172,9 @@ class AttachmentsPreviewFragment @Inject constructor(
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
val uri = currentAttachment.queryUri
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), currentAttachment.name)
.start(requireContext(), this)
createUCropWithDefaultSettings(colorProvider, uri, destinationFile.toUri(), currentAttachment.name)
.getIntent(requireContext())
.let { intent -> uCropActivityResultLauncher.launch(intent) }
}
private fun setupRecyclerViews() {

View File

@ -24,7 +24,6 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.resources.ColorProvider
import kotlinx.android.synthetic.main.activity_big_image_viewer.*
import javax.inject.Inject
@ -33,7 +32,6 @@ import javax.inject.Inject
*/
class BigImageViewerActivity : VectorBaseActivity() {
@Inject lateinit var sessionHolder: ActiveSessionHolder
@Inject lateinit var colorProvider: ColorProvider
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)

View File

@ -16,16 +16,17 @@
package im.vector.app.features.media
import android.content.Context
import android.graphics.Color
import android.net.Uri
import androidx.core.content.ContextCompat
import com.yalantis.ucrop.UCrop
import com.yalantis.ucrop.UCropActivity
import im.vector.app.R
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.core.resources.ColorProvider
fun createUCropWithDefaultSettings(context: Context, source: Uri, destination: Uri, toolbarTitle: String?): UCrop {
fun createUCropWithDefaultSettings(colorProvider: ColorProvider,
source: Uri,
destination: Uri,
toolbarTitle: String?): UCrop {
return UCrop.of(source, destination)
.withOptions(
UCrop.Options()
@ -39,15 +40,15 @@ fun createUCropWithDefaultSettings(context: Context, source: Uri, destination: U
// Disable freestyle crop, usability was not easy
// setFreeStyleCropEnabled(true)
// Color used for toolbar icon and text
setToolbarColor(ThemeUtils.getColor(context, R.attr.riotx_background))
setToolbarWidgetColor(ThemeUtils.getColor(context, R.attr.vctr_toolbar_primary_text_color))
setToolbarColor(colorProvider.getColorFromAttribute(R.attr.riotx_background))
setToolbarWidgetColor(colorProvider.getColorFromAttribute(R.attr.vctr_toolbar_primary_text_color))
// Background
setRootViewBackgroundColor(ThemeUtils.getColor(context, R.attr.riotx_background))
setRootViewBackgroundColor(colorProvider.getColorFromAttribute(R.attr.riotx_background))
// Status bar color (pb in dark mode, icon of the status bar are dark)
setStatusBarColor(ThemeUtils.getColor(context, R.attr.riotx_header_panel_background))
setStatusBarColor(colorProvider.getColorFromAttribute(R.attr.riotx_header_panel_background))
// Known issue: there is still orange color used by the lib
// https://github.com/Yalantis/uCrop/issues/602
setActiveControlsWidgetColor(ContextCompat.getColor(context, R.color.riotx_accent))
setActiveControlsWidgetColor(colorProvider.getColor(R.color.riotx_accent))
// Hide the logo (does not work)
setLogoColor(Color.TRANSPARENT)
}

View File

@ -16,32 +16,30 @@
package im.vector.app.features.roomdirectory.createroom
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.core.net.toUri
import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.yalantis.ucrop.UCrop
import im.vector.app.R
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.media.createUCropWithDefaultSettings
import im.vector.app.core.resources.ColorProvider
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.lib.multipicker.entity.MultiPickerImageType
import kotlinx.android.synthetic.main.fragment_create_room.*
import timber.log.Timber
import java.io.File
import javax.inject.Inject
class CreateRoomFragment @Inject constructor(
private val createRoomController: CreateRoomController
private val createRoomController: CreateRoomController,
colorProvider: ColorProvider
) : VectorBaseFragment(),
CreateRoomController.Listener,
GalleryOrCameraDialogHelper.Listener,
@ -50,7 +48,7 @@ class CreateRoomFragment @Inject constructor(
private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel
private val viewModel: CreateRoomViewModel by activityViewModel()
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this)
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
override fun getLayoutResId() = R.layout.fragment_create_room
@ -62,6 +60,11 @@ class CreateRoomFragment @Inject constructor(
createRoomClose.debouncedClicks {
sharedActionViewModel.post(RoomDirectorySharedAction.Back)
}
viewModel.observeViewEvents {
when (it) {
CreateRoomViewEvents.Quit -> vectorBaseActivity.onBackPressed()
}.exhaustive
}
}
override fun onDestroyView() {
@ -83,25 +86,8 @@ class CreateRoomFragment @Inject constructor(
galleryOrCameraDialogHelper.show()
}
override fun onImageReady(image: MultiPickerImageType) {
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
val uri = image.contentUri
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), image.displayName)
.withAspectRatio(1f, 1f)
.start(requireContext(), this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
UCrop.REQUEST_CROP ->
viewModel.handle(CreateRoomAction.SetAvatar(data?.let { UCrop.getOutput(it) }))
}
}
override fun onImageReady(uri: Uri?) {
viewModel.handle(CreateRoomAction.SetAvatar(uri))
}
override fun onNameChange(newName: String) {
@ -134,8 +120,21 @@ class CreateRoomFragment @Inject constructor(
}
override fun onBackPressed(toolbarButton: Boolean): Boolean {
viewModel.handle(CreateRoomAction.Reset)
return false
return withState(viewModel) {
return@withState if (!it.isEmpty()) {
AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_title_warning)
.setMessage(R.string.warning_room_not_created_yet)
.setPositiveButton(R.string.yes) { _, _ ->
viewModel.handle(CreateRoomAction.Reset)
}
.setNegativeButton(R.string.no, null)
.show()
true
} else {
false
}
}
}
override fun invalidate() = withState(viewModel) { state ->

View File

@ -0,0 +1,26 @@
/*
* 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.roomdirectory.createroom
import im.vector.app.core.platform.VectorViewEvents
/**
* Transient events for room creation screen
*/
sealed class CreateRoomViewEvents : VectorViewEvents {
object Quit : CreateRoomViewEvents()
}

View File

@ -16,6 +16,7 @@
package im.vector.app.features.roomdirectory.createroom
import androidx.core.net.toFile
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
@ -27,7 +28,6 @@ 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.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isE2EByDefault
@ -45,7 +45,7 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState,
private val session: Session,
private val rawService: RawService
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, EmptyViewEvents>(initialState) {
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
@ -104,11 +104,16 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
private fun doReset() {
setState {
// Delete temporary file with the avatar
avatarUri?.let { tryOrNull { it.toFile().delete() } }
CreateRoomViewState(
isEncrypted = adminE2EByDefault,
hsAdminHasDisabledE2E = !adminE2EByDefault
)
}
_viewEvents.post(CreateRoomViewEvents.Quit)
}
private fun setAvatar(action: CreateRoomAction.SetAvatar) = setState { copy(avatarUri = action.imageUri) }

View File

@ -30,4 +30,10 @@ data class CreateRoomViewState(
val isEncrypted: Boolean = false,
val hsAdminHasDisabledE2E: Boolean = false,
val asyncCreateRoomRequest: Async<String> = Uninitialized
) : MvRxState
) : MvRxState {
/**
* Return true if there is not important input from user
*/
fun isEmpty() = avatarUri == null && roomName.isEmpty() && roomTopic.isEmpty()
}

View File

@ -27,4 +27,5 @@ sealed class RoomSettingsAction : VectorViewModelAction {
data class SetRoomCanonicalAlias(val newCanonicalAlias: String) : RoomSettingsAction()
object EnableEncryption : RoomSettingsAction()
object Save : RoomSettingsAction()
object Cancel : RoomSettingsAction()
}

View File

@ -16,19 +16,16 @@
package im.vector.app.features.roomprofile.settings
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.core.view.isVisible
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.yalantis.ucrop.UCrop
import im.vector.app.R
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.extensions.cleanup
@ -37,19 +34,17 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.intent.getFilenameFromUri
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.toast
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter
import im.vector.app.features.media.createUCropWithDefaultSettings
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.lib.multipicker.entity.MultiPickerImageType
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
import org.matrix.android.sdk.api.util.toMatrixItem
import java.io.File
import java.util.UUID
import javax.inject.Inject
@ -57,6 +52,7 @@ class RoomSettingsFragment @Inject constructor(
val viewModelFactory: RoomSettingsViewModel.Factory,
private val controller: RoomSettingsController,
private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter,
colorProvider: ColorProvider,
private val avatarRenderer: AvatarRenderer
) :
VectorBaseFragment(),
@ -66,7 +62,7 @@ class RoomSettingsFragment @Inject constructor(
private val viewModel: RoomSettingsViewModel by fragmentViewModel()
private val roomProfileArgs: RoomProfileArgs by args()
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this)
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
@ -83,7 +79,11 @@ class RoomSettingsFragment @Inject constructor(
viewModel.observeViewEvents {
when (it) {
is RoomSettingsViewEvents.Failure -> showFailure(it.throwable)
is RoomSettingsViewEvents.Success -> showSuccess()
RoomSettingsViewEvents.Success -> showSuccess()
RoomSettingsViewEvents.GoBack -> {
ignoreChanges = true
vectorBaseActivity.onBackPressed()
}
}.exhaustive
}
}
@ -178,12 +178,15 @@ class RoomSettingsFragment @Inject constructor(
viewModel.handle(RoomSettingsAction.SetRoomCanonicalAlias(alias))
}
override fun onImageReady(image: MultiPickerImageType) {
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
val uri = image.contentUri
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), image.displayName)
.withAspectRatio(1f, 1f)
.start(requireContext(), this)
override fun onImageReady(uri: Uri?) {
uri ?: return
viewModel.handle(
RoomSettingsAction.SetAvatarAction(
RoomSettingsViewState.AvatarAction.UpdateAvatar(
newAvatarUri = uri,
newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString())
)
)
}
override fun onAvatarDelete() {
@ -208,26 +211,6 @@ class RoomSettingsFragment @Inject constructor(
galleryOrCameraDialogHelper.show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
UCrop.REQUEST_CROP -> {
val uri = data?.let { UCrop.getOutput(it) } ?: return
viewModel.handle(RoomSettingsAction.SetAvatarAction(
RoomSettingsViewState.AvatarAction.UpdateAvatar(
newAvatarUri = uri,
newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString())
)
)
}
}
}
}
private var ignoreChanges = false
override fun onBackPressed(toolbarButton: Boolean): Boolean {
@ -239,8 +222,7 @@ class RoomSettingsFragment @Inject constructor(
.setTitle(R.string.dialog_title_warning)
.setMessage(R.string.warning_unsaved_change)
.setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ ->
ignoreChanges = true
vectorBaseActivity.onBackPressed()
viewModel.handle(RoomSettingsAction.Cancel)
}
.setNegativeButton(R.string.cancel, null)
.show()

View File

@ -25,4 +25,5 @@ import im.vector.app.core.platform.VectorViewEvents
sealed class RoomSettingsViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : RoomSettingsViewEvents()
object Success : RoomSettingsViewEvents()
object GoBack : RoomSettingsViewEvents()
}

View File

@ -16,6 +16,7 @@
package im.vector.app.features.roomprofile.settings
import androidx.core.net.toFile
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
@ -27,6 +28,7 @@ import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import io.reactivex.Completable
import io.reactivex.Observable
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
@ -140,15 +142,35 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
override fun handle(action: RoomSettingsAction) {
when (action) {
is RoomSettingsAction.EnableEncryption -> handleEnableEncryption()
is RoomSettingsAction.SetAvatarAction -> setState { copy(avatarAction = action.avatarAction) }
is RoomSettingsAction.SetAvatarAction -> handleSetAvatarAction(action)
is RoomSettingsAction.SetRoomName -> setState { copy(newName = action.newName) }
is RoomSettingsAction.SetRoomTopic -> setState { copy(newTopic = action.newTopic) }
is RoomSettingsAction.SetRoomHistoryVisibility -> setState { copy(newHistoryVisibility = action.visibility) }
is RoomSettingsAction.SetRoomCanonicalAlias -> setState { copy(newCanonicalAlias = action.newCanonicalAlias) }
is RoomSettingsAction.Save -> saveSettings()
is RoomSettingsAction.Cancel -> cancel()
}.exhaustive
}
private fun handleSetAvatarAction(action: RoomSettingsAction.SetAvatarAction) {
deletePendingAvatar()
setState { copy(avatarAction = action.avatarAction) }
}
private fun deletePendingAvatar() {
// Maybe delete the pending avatar
withState {
(it.avatarAction as? RoomSettingsViewState.AvatarAction.UpdateAvatar)
?.let { tryOrNull { it.newAvatarUri.toFile().delete() } }
}
}
private fun cancel() {
deletePendingAvatar()
_viewEvents.post(RoomSettingsViewEvents.GoBack)
}
private fun saveSettings() = withState { state ->
postLoading(true)
@ -188,6 +210,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
{
postLoading(false)
setState { copy(newHistoryVisibility = null) }
deletePendingAvatar()
_viewEvents.post(RoomSettingsViewEvents.Success)
},
{

View File

@ -18,8 +18,6 @@
package im.vector.app.features.settings
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.Editable
@ -28,7 +26,6 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.preference.EditTextPreference
import androidx.preference.Preference
@ -38,7 +35,6 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.cache.DiskCache
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.yalantis.ucrop.UCrop
import im.vector.app.R
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.extensions.hideKeyboard
@ -48,14 +44,13 @@ import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.core.preference.UserAvatarPreference
import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.TextUtils
import im.vector.app.core.utils.getSizeOfFiles
import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
import im.vector.app.features.media.createUCropWithDefaultSettings
import im.vector.app.features.workers.signout.SignOutUiWorker
import im.vector.lib.multipicker.entity.MultiPickerImageType
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -70,15 +65,18 @@ import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap
import java.io.File
import java.util.UUID
import javax.inject.Inject
class VectorSettingsGeneralFragment :
class VectorSettingsGeneralFragment @Inject constructor(
colorProvider: ColorProvider
):
VectorSettingsBaseFragment(),
GalleryOrCameraDialogHelper.Listener {
override var titleRes = R.string.settings_general_title
override val preferenceXmlRes = R.xml.vector_settings_general
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this)
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
private val mUserSettingsCategory by lazy {
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_USER_SETTINGS_PREFERENCE_KEY)!!
@ -277,18 +275,6 @@ class VectorSettingsGeneralFragment :
session.integrationManagerService().removeListener(integrationServiceListener)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
}
}
}
private fun refreshIntegrationManagerSettings() {
val integrationAllowed = session.integrationManagerService().isIntegrationEnabled()
(findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ALLOW_INTEGRATIONS_KEY))!!.let {
@ -308,15 +294,7 @@ class VectorSettingsGeneralFragment :
}
}
override fun onImageReady(image: MultiPickerImageType) {
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
val uri = image.contentUri
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), image.displayName)
.withAspectRatio(1f, 1f)
.start(requireContext(), this)
}
private fun onAvatarCropped(uri: Uri?) {
override fun onImageReady(uri: Uri?) {
if (uri != null) {
uploadAvatar(uri)
} else {

View File

@ -1840,12 +1840,14 @@
<string name="error_file_too_big">"The file '%1$s' (%2$s) is too large to upload. The limit is %3$s."</string>
<string name="error_attachment">"An error occurred while retrieving the attachment."</string>
<string name="attachment_type_dialog_title">"Add image from"</string>
<string name="attachment_type_file">"File"</string>
<string name="attachment_type_contact">"Contact"</string>
<string name="attachment_type_camera">"Camera"</string>
<string name="attachment_type_audio">"Audio"</string>
<string name="attachment_type_gallery">"Gallery"</string>
<string name="attachment_type_sticker">"Sticker"</string>
<string name="rotate_and_crop_screen_title">Rotate and crop</string>
<string name="error_handling_incoming_share">Couldn\'t handle share data</string>
<string name="uploads_media_title">MEDIA</string>
@ -2630,6 +2632,7 @@
<!-- Universal link -->
<string name="universal_link_malformed">The link was malformed</string>
<string name="warning_room_not_created_yet">The room is not yet created. Cancel the room creation?</string>
<string name="warning_unsaved_change">There are unsaved changes. Discard the changes?</string>
<string name="warning_unsaved_change_discard">Discard changes</string>
</resources>