Merge pull request #2285 from vector-im/feature/bma/uCrop
Feature/bma/u crop
This commit is contained in:
commit
f127a75e38
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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) }
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -27,4 +27,5 @@ sealed class RoomSettingsAction : VectorViewModelAction {
|
|||
data class SetRoomCanonicalAlias(val newCanonicalAlias: String) : RoomSettingsAction()
|
||||
object EnableEncryption : RoomSettingsAction()
|
||||
object Save : RoomSettingsAction()
|
||||
object Cancel : RoomSettingsAction()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue