From e3c1dee5271f819677c84584c68fb3fce1456120 Mon Sep 17 00:00:00 2001 From: Yamil Medina Date: Thu, 7 Nov 2024 12:51:51 -0300 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20improve=20logging=20for=20backups?= =?UTF-8?q?=20=F0=9F=8D=92=20(WPB-12113)=20(#3608)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/home/settings/backup/BackupAndRestoreViewModel.kt | 7 ++----- kalium | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreViewModel.kt index c597f60e929..357a47fb1d5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreViewModel.kt @@ -212,7 +212,7 @@ class BackupAndRestoreViewModel restoreFileValidation = RestoreFileValidation.ValidNonEncryptedBackup, backupRestoreProgress = BackupRestoreProgress.InProgress(PROGRESS_75) ) - when (importBackup(importedBackupPath, null)) { + when (val result = importBackup(importedBackupPath, null)) { RestoreBackupResult.Success -> { updateCreationProgress(PROGRESS_75) delay(SMALL_DELAY) @@ -221,10 +221,7 @@ class BackupAndRestoreViewModel } is RestoreBackupResult.Failure -> { - appLogger.e( - "Error when restoring the db file. The format or version of the backup is not compatible with this " + - "version of the app" - ) + appLogger.e("Error when restoring the backup db file caused by: ${result.failure.cause}") state = state.copy( restoreFileValidation = RestoreFileValidation.IncompatibleBackup, backupRestoreProgress = BackupRestoreProgress.Failed diff --git a/kalium b/kalium index 77bf60957cd..f417fd46f63 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 77bf60957cdc50e9b81d9327883adc2b93285472 +Subproject commit f417fd46f63b0e2c82faf4f86ed115718829f1c6 From 9d69cf63ecea08182655364ea42c79f0e1080f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Zag=C3=B3rski?= Date: Thu, 7 Nov 2024 22:03:20 +0100 Subject: [PATCH 2/5] fix: Username index out of bounds exception [WPB-12143] (#3603) --- .../android/ui/common/textfield/InputTransformations.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/textfield/InputTransformations.kt b/app/src/main/kotlin/com/wire/android/ui/common/textfield/InputTransformations.kt index 08c1e02c4da..e6c31c049ca 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/textfield/InputTransformations.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/textfield/InputTransformations.kt @@ -90,7 +90,11 @@ fun InputTransformation.forceLowercase(): InputTransformation = class ForceLowercaseTransformation : InputTransformation { override fun TextFieldBuffer.transformInput() { - replace(0, length, asCharSequence().toString().lowercase()) + val currentText = asCharSequence().toString() + val lowercasedText = currentText.lowercase() + if (currentText != lowercasedText) { + replace(0, length, lowercasedText) + } } } From 121fb3e96f9dad58c4b4e6a94456ce4368c5948c Mon Sep 17 00:00:00 2001 From: Yamil Medina Date: Fri, 8 Nov 2024 05:58:30 -0300 Subject: [PATCH 3/5] feat: strip metadata from profile pictures (WPB-11170) (#3590) --- .../userprofile/avatarpicker/AvatarPicker.kt | 35 +-- .../avatarpicker/AvatarPickerViewModel.kt | 18 +- .../self/SelfUserProfileViewModel.kt | 32 --- .../com/wire/android/util/ExifHandler.kt | 218 ++++++++++++++++++ .../kotlin/com/wire/android/util/FileUtil.kt | 13 +- .../kotlin/com/wire/android/util/ImageUtil.kt | 93 ++++---- .../image/AvatarPickerViewModelTest.kt | 19 +- .../SelfUserProfileViewModelArrangement.kt | 10 - .../com/wire/android/util/ImageUtilTest.kt | 42 ++++ app/src/test/resources/rich-exif-sample.jpg | Bin 0 -> 159137 bytes 10 files changed, 348 insertions(+), 132 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/util/ExifHandler.kt create mode 100644 app/src/test/kotlin/com/wire/android/util/ImageUtilTest.kt create mode 100644 app/src/test/resources/rich-exif-sample.jpg diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt index edcaa666a62..2b87fbc633e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt @@ -18,8 +18,6 @@ package com.wire.android.ui.userprofile.avatarpicker -import android.content.Context -import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -33,10 +31,8 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.net.toUri @@ -65,13 +61,6 @@ import com.wire.android.ui.common.visbility.rememberVisibilityState import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.userprofile.avatarpicker.AvatarPickerViewModel.PictureState -import com.wire.android.util.ImageUtil -import com.wire.android.util.resampleImageAndCopyToTempPath -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import okio.Path @RootNavGraph @WireDestination @@ -84,18 +73,15 @@ fun AvatarPickerScreen( val permissionPermanentlyDeniedDialogState = rememberVisibilityState() - val context = LocalContext.current - val targetAvatarPath = viewModel.defaultAvatarPath val targetAvatarUri = viewModel.temporaryAvatarUri - val scope = rememberCoroutineScope() val state = rememberAvatarPickerState( onImageSelected = { originalUri -> - onNewAvatarPicked(originalUri, targetAvatarPath, scope, context, viewModel) + viewModel.updatePickedAvatarUri(originalUri, targetAvatarPath.toFile().toUri()) }, onPictureTaken = { - onNewAvatarPicked(targetAvatarUri, targetAvatarPath, scope, context, viewModel) + viewModel.updatePickedAvatarUri(targetAvatarUri, targetAvatarPath.toFile().toUri()) }, targetPictureFileUri = targetAvatarUri, onGalleryPermissionPermanentlyDenied = { @@ -141,19 +127,6 @@ fun AvatarPickerScreen( ) } -// TODO: Mateusz: I think we should refactor this, it takes some values from the ViewModel, part of the logic is executed inside -// the UI, part of the logic is exectued inside the ViewModel, I see no reasons to handle the logic inside the UI -// personally it was a confusing part for me to read when investing the bugs, unless there is a valid reason to move the logic to the UI -// that I am not aware of ? -fun onNewAvatarPicked(originalUri: Uri, targetAvatarPath: Path, scope: CoroutineScope, context: Context, viewModel: AvatarPickerViewModel) { - scope.launch { - sanitizeAvatarImage(originalUri, targetAvatarPath, context) - withContext(Dispatchers.Main) { - viewModel.updatePickedAvatarUri(targetAvatarPath.toFile().toUri()) - } - } -} - @Composable private fun AvatarPickerContent( pictureState: PictureState, @@ -287,7 +260,3 @@ private fun AvatarPickerTopBar(onCloseClick: () -> Unit) { title = stringResource(R.string.profile_image_top_bar_label), ) } - -private suspend fun sanitizeAvatarImage(originalAvatarUri: Uri, avatarPath: Path, appContext: Context) { - originalAvatarUri.resampleImageAndCopyToTempPath(appContext, avatarPath, ImageUtil.ImageSizeClass.Small) -} diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt index 1a73eab11c6..c1a0a26bb89 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt @@ -32,7 +32,9 @@ import com.wire.android.appLogger import com.wire.android.datastore.UserDataStore import com.wire.android.model.SnackBarMessage import com.wire.android.util.AvatarImageManager +import com.wire.android.util.ImageUtil import com.wire.android.util.dispatchers.DispatcherProvider +import com.wire.android.util.resampleImageAndCopyToTempPath import com.wire.android.util.toByteArray import com.wire.android.util.ui.UIText import com.wire.kalium.logic.NetworkFailure @@ -104,10 +106,24 @@ class AvatarPickerViewModel @Inject constructor( } } - fun updatePickedAvatarUri(updatedUri: Uri) = viewModelScope.launch(dispatchers.main()) { + fun updatePickedAvatarUri(originalUri: Uri, updatedUri: Uri) = viewModelScope.launch { + sanitizeAvatarImage(originalUri, defaultAvatarPath) pictureState = PictureState.Picked(updatedUri) } + /** + * Resamples the image and removes unnecessary metadata before uploading it. + * This to avoid uploading unnecessarily large images for profile pictures and sensitive metadata. + */ + private suspend fun sanitizeAvatarImage(originalAvatarUri: Uri, avatarPath: Path) { + originalAvatarUri.resampleImageAndCopyToTempPath( + context = appContext, + tempCachePath = avatarPath, + sizeClass = ImageUtil.ImageSizeClass.Small, + shouldRemoveMetadata = true + ) + } + fun uploadNewPickedAvatar(onComplete: (avatarAssetId: String?) -> Unit) { val imgUri = pictureState.avatarUri diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt index 99d9ec2889d..85f41060d40 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt @@ -23,11 +23,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.wire.android.BuildConfig import com.wire.android.appLogger import com.wire.android.datastore.GlobalDataStore import com.wire.android.datastore.UserDataStore -import com.wire.android.di.AuthServerConfigProvider import com.wire.android.di.CurrentAccount import com.wire.android.feature.AccountSwitchUseCase import com.wire.android.feature.SwitchAccountActions @@ -42,7 +40,6 @@ import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.ui.userprofile.self.dialog.StatusDialogData import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.ui.WireSessionImageLoader -import com.wire.kalium.logic.configuration.server.ServerConfig import com.wire.kalium.logic.data.call.Call import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.data.id.toQualifiedID @@ -61,7 +58,6 @@ import com.wire.kalium.logic.feature.team.GetUpdatedSelfTeamUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.IsReadOnlyAccountUseCase import com.wire.kalium.logic.feature.user.ObserveValidAccountsUseCase -import com.wire.kalium.logic.feature.user.SelfServerConfigUseCase import com.wire.kalium.logic.feature.user.UpdateSelfAvailabilityStatusUseCase import com.wire.kalium.logic.functional.getOrNull import dagger.hilt.android.lifecycle.HiltViewModel @@ -94,8 +90,6 @@ class SelfUserProfileViewModel @Inject constructor( private val observeLegalHoldStatusForSelfUser: ObserveLegalHoldStateForSelfUserUseCase, private val dispatchers: DispatcherProvider, private val wireSessionImageLoader: WireSessionImageLoader, - private val authServerConfigProvider: AuthServerConfigProvider, - private val selfServerLinks: SelfServerConfigUseCase, private val otherAccountMapper: OtherAccountMapper, private val observeEstablishedCalls: ObserveEstablishedCallsUseCase, private val accountSwitch: AccountSwitchUseCase, @@ -266,27 +260,6 @@ class SelfUserProfileViewModel @Inject constructor( } } - // todo. cleanup unused code - fun tryToInitAddingAccount(onSucceeded: () -> Unit) { - viewModelScope.launch { - // the total number of accounts is otherAccounts + 1 for the current account - val canAddNewAccounts: Boolean = (userProfileState.otherAccounts.size + 1) < BuildConfig.MAX_ACCOUNTS - - if (!canAddNewAccounts) { - userProfileState = userProfileState.copy(maxAccountsReached = true) - return@launch - } - - val selfServerLinks: ServerConfig.Links = - when (val result = selfServerLinks()) { - is SelfServerConfigUseCase.Result.Failure -> return@launch - is SelfServerConfigUseCase.Result.Success -> result.serverLinks.links - } - authServerConfigProvider.updateAuthServer(selfServerLinks) - onSucceeded() - } - } - fun dismissStatusDialog() { userProfileState = userProfileState.copy(statusDialogData = null) } @@ -321,11 +294,6 @@ class SelfUserProfileViewModel @Inject constructor( } } - // todo. cleanup unused code - fun onMaxAccountReachedDialogDismissed() { - userProfileState = userProfileState.copy(maxAccountsReached = false) - } - private fun setNotShowStatusRationaleAgainIfNeeded(status: UserAvailabilityStatus) { userProfileState.statusDialogData.let { dialogState -> if (dialogState?.isCheckBoxChecked == true) { diff --git a/app/src/main/kotlin/com/wire/android/util/ExifHandler.kt b/app/src/main/kotlin/com/wire/android/util/ExifHandler.kt new file mode 100644 index 00000000000..eaf8c4b05b5 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/ExifHandler.kt @@ -0,0 +1,218 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.util + +import android.graphics.Bitmap +import android.graphics.Matrix +import androidx.exifinterface.media.ExifInterface +import com.wire.android.appLogger + +/** + * Used to remove unnecessary metadata from the image. + */ +fun Bitmap.removeExifMetadata(exif: ExifInterface): Bitmap { + removableExifAttributes.forEach { + exif.setAttribute(it, null) + } + return this +} + +/** + * Rotates the image to its [ExifInterface.ORIENTATION_NORMAL] in case it's rotated with a different orientation than + * landscape or portrait See more about exif interface at: + * https://developer.android.com/reference/androidx/exifinterface/media/ExifInterface + * + * @param exif Exif interface for of the image to rotate + * @return Bitmap the rotated bitmap or the same in case there is no rotation performed + */ +@Suppress("MagicNumber", "TooGenericExceptionCaught") +fun Bitmap.rotateImageToNormalOrientation(exif: ExifInterface?): Bitmap { + val orientation = exif?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) + val matrix = Matrix() + when (orientation) { + ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f) + ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f) + ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f) + else -> return this + } + + return try { + val rotated = Bitmap.createBitmap(this, 0, 0, this.width, this.height, matrix, true) + this.recycle() + rotated + } catch (exception: Exception) { + appLogger.withTextTag("ExifHandler").w("Failed to rotate image to normal orientation", exception) + this + } +} + +private val removableExifAttributes = arrayOf( + // These 3, we don't remove as "might be" reused when resampling. + // ExifInterface.TAG_ORIENTATION + // ExifInterface.TAG_IMAGE_WIDTH, + // ExifInterface.TAG_IMAGE_LENGTH, + ExifInterface.TAG_BITS_PER_SAMPLE, + ExifInterface.TAG_COMPRESSION, + ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, + ExifInterface.TAG_SAMPLES_PER_PIXEL, + ExifInterface.TAG_PLANAR_CONFIGURATION, + ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, + ExifInterface.TAG_Y_CB_CR_POSITIONING, + ExifInterface.TAG_X_RESOLUTION, + ExifInterface.TAG_Y_RESOLUTION, + ExifInterface.TAG_RESOLUTION_UNIT, + ExifInterface.TAG_STRIP_OFFSETS, + ExifInterface.TAG_ROWS_PER_STRIP, + ExifInterface.TAG_STRIP_BYTE_COUNTS, + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, + ExifInterface.TAG_TRANSFER_FUNCTION, + ExifInterface.TAG_WHITE_POINT, + ExifInterface.TAG_PRIMARY_CHROMATICITIES, + ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, + ExifInterface.TAG_REFERENCE_BLACK_WHITE, + ExifInterface.TAG_DATETIME, + ExifInterface.TAG_IMAGE_DESCRIPTION, + ExifInterface.TAG_MAKE, + ExifInterface.TAG_MODEL, + ExifInterface.TAG_SOFTWARE, + ExifInterface.TAG_ARTIST, + ExifInterface.TAG_COPYRIGHT, + ExifInterface.TAG_EXIF_VERSION, + ExifInterface.TAG_FLASHPIX_VERSION, + ExifInterface.TAG_COLOR_SPACE, + ExifInterface.TAG_GAMMA, + ExifInterface.TAG_PIXEL_X_DIMENSION, + ExifInterface.TAG_PIXEL_Y_DIMENSION, + ExifInterface.TAG_COMPONENTS_CONFIGURATION, + ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, + ExifInterface.TAG_MAKER_NOTE, + ExifInterface.TAG_USER_COMMENT, + ExifInterface.TAG_RELATED_SOUND_FILE, + ExifInterface.TAG_DATETIME_ORIGINAL, + ExifInterface.TAG_DATETIME_DIGITIZED, + ExifInterface.TAG_OFFSET_TIME, + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, + ExifInterface.TAG_OFFSET_TIME_DIGITIZED, + ExifInterface.TAG_SUBSEC_TIME, + ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, + ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, + ExifInterface.TAG_EXPOSURE_TIME, + ExifInterface.TAG_F_NUMBER, + ExifInterface.TAG_EXPOSURE_PROGRAM, + ExifInterface.TAG_SPECTRAL_SENSITIVITY, + ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY, + ExifInterface.TAG_OECF, + ExifInterface.TAG_SENSITIVITY_TYPE, + ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, + ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, + ExifInterface.TAG_ISO_SPEED, + ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, + ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, + ExifInterface.TAG_SHUTTER_SPEED_VALUE, + ExifInterface.TAG_APERTURE_VALUE, + ExifInterface.TAG_BRIGHTNESS_VALUE, + ExifInterface.TAG_EXPOSURE_BIAS_VALUE, + ExifInterface.TAG_MAX_APERTURE_VALUE, + ExifInterface.TAG_SUBJECT_DISTANCE, + ExifInterface.TAG_METERING_MODE, + ExifInterface.TAG_LIGHT_SOURCE, + ExifInterface.TAG_FLASH, + ExifInterface.TAG_SUBJECT_AREA, + ExifInterface.TAG_FOCAL_LENGTH, + ExifInterface.TAG_FLASH_ENERGY, + ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, + ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, + ExifInterface.TAG_SUBJECT_LOCATION, + ExifInterface.TAG_EXPOSURE_INDEX, + ExifInterface.TAG_SENSING_METHOD, + ExifInterface.TAG_FILE_SOURCE, + ExifInterface.TAG_SCENE_TYPE, + ExifInterface.TAG_CFA_PATTERN, + ExifInterface.TAG_CUSTOM_RENDERED, + ExifInterface.TAG_EXPOSURE_MODE, + ExifInterface.TAG_WHITE_BALANCE, + ExifInterface.TAG_DIGITAL_ZOOM_RATIO, + ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, + ExifInterface.TAG_SCENE_CAPTURE_TYPE, + ExifInterface.TAG_GAIN_CONTROL, + ExifInterface.TAG_CONTRAST, + ExifInterface.TAG_SATURATION, + ExifInterface.TAG_SHARPNESS, + ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, + ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, + ExifInterface.TAG_IMAGE_UNIQUE_ID, + ExifInterface.TAG_CAMERA_OWNER_NAME, + ExifInterface.TAG_BODY_SERIAL_NUMBER, + ExifInterface.TAG_LENS_SPECIFICATION, + ExifInterface.TAG_LENS_MAKE, + ExifInterface.TAG_LENS_MODEL, + ExifInterface.TAG_LENS_SERIAL_NUMBER, + ExifInterface.TAG_GPS_VERSION_ID, + ExifInterface.TAG_GPS_LATITUDE_REF, + ExifInterface.TAG_GPS_LATITUDE, + ExifInterface.TAG_GPS_LONGITUDE_REF, + ExifInterface.TAG_GPS_LONGITUDE, + ExifInterface.TAG_GPS_ALTITUDE_REF, + ExifInterface.TAG_GPS_ALTITUDE, + ExifInterface.TAG_GPS_TIMESTAMP, + ExifInterface.TAG_GPS_SATELLITES, + ExifInterface.TAG_GPS_STATUS, + ExifInterface.TAG_GPS_MEASURE_MODE, + ExifInterface.TAG_GPS_DOP, + ExifInterface.TAG_GPS_SPEED_REF, + ExifInterface.TAG_GPS_SPEED, + ExifInterface.TAG_GPS_TRACK_REF, + ExifInterface.TAG_GPS_TRACK, + ExifInterface.TAG_GPS_IMG_DIRECTION_REF, + ExifInterface.TAG_GPS_IMG_DIRECTION, + ExifInterface.TAG_GPS_MAP_DATUM, + ExifInterface.TAG_GPS_DEST_LATITUDE_REF, + ExifInterface.TAG_GPS_DEST_LATITUDE, + ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, + ExifInterface.TAG_GPS_DEST_LONGITUDE, + ExifInterface.TAG_GPS_DEST_BEARING_REF, + ExifInterface.TAG_GPS_DEST_BEARING, + ExifInterface.TAG_GPS_DEST_DISTANCE_REF, + ExifInterface.TAG_GPS_DEST_DISTANCE, + ExifInterface.TAG_GPS_PROCESSING_METHOD, + ExifInterface.TAG_GPS_AREA_INFORMATION, + ExifInterface.TAG_GPS_DATESTAMP, + ExifInterface.TAG_GPS_DIFFERENTIAL, + ExifInterface.TAG_GPS_H_POSITIONING_ERROR, + ExifInterface.TAG_INTEROPERABILITY_INDEX, + ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH, + ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH, + ExifInterface.TAG_DNG_VERSION, + ExifInterface.TAG_DEFAULT_CROP_SIZE, + ExifInterface.TAG_ORF_THUMBNAIL_IMAGE, + ExifInterface.TAG_ORF_PREVIEW_IMAGE_START, + ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH, + ExifInterface.TAG_ORF_ASPECT_FRAME, + ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER, + ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER, + ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER, + ExifInterface.TAG_RW2_SENSOR_TOP_BORDER, + ExifInterface.TAG_RW2_ISO, + ExifInterface.TAG_RW2_JPG_FROM_RAW, + ExifInterface.TAG_XMP, + ExifInterface.TAG_NEW_SUBFILE_TYPE, + ExifInterface.TAG_SUBFILE_TYPE, +) diff --git a/app/src/main/kotlin/com/wire/android/util/FileUtil.kt b/app/src/main/kotlin/com/wire/android/util/FileUtil.kt index 0eb6c8c44fa..64cc1321f3d 100644 --- a/app/src/main/kotlin/com/wire/android/util/FileUtil.kt +++ b/app/src/main/kotlin/com/wire/android/util/FileUtil.kt @@ -195,10 +195,20 @@ fun Uri.getMimeType(context: Context): String? { return mimeType } +/** + * Resamples the images if needed and copies them to the temp path [tempCachePath] + * If desired, the metadata can be removed from the image according to [shouldRemoveMetadata] + * + * @param context the context + * @param tempCachePath the path where the image will be copied + * @param sizeClass the desired size class of the image [ImageSizeClass] + * @param shouldRemoveMetadata whether to remove metadata from the image defaults to false + */ suspend fun Uri.resampleImageAndCopyToTempPath( context: Context, tempCachePath: Path, sizeClass: ImageSizeClass = Medium, + shouldRemoveMetadata: Boolean = false, dispatcher: DispatcherProvider = DefaultDispatcherProvider() ): Long { return withContext(dispatcher.io()) { @@ -211,7 +221,8 @@ suspend fun Uri.resampleImageAndCopyToTempPath( // If the GIF is too large, the user will be informed about that, just like for all other files. originalImage.writeToFile(tempCachePath.toFile()) } else { - ImageUtil.resample(originalImage, sizeClass).writeToFile(tempCachePath.toFile()) + ImageUtil.resample(originalImage, sizeClass, shouldRemoveMetadata) + .writeToFile(tempCachePath.toFile()) } } } diff --git a/app/src/main/kotlin/com/wire/android/util/ImageUtil.kt b/app/src/main/kotlin/com/wire/android/util/ImageUtil.kt index b875aa6d64a..508dd2237db 100644 --- a/app/src/main/kotlin/com/wire/android/util/ImageUtil.kt +++ b/app/src/main/kotlin/com/wire/android/util/ImageUtil.kt @@ -23,7 +23,6 @@ import android.content.ContentResolver.SCHEME_FILE import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.graphics.Matrix import android.net.Uri import androidx.exifinterface.media.ExifInterface import com.wire.kalium.logic.data.asset.KaliumFileSystem @@ -58,32 +57,67 @@ object ImageUtil { /** * Resamples, downscales and normalizes rotation of an image based on its intended [ImageSizeClass] use. + * Also takes care of removing metadata before resampling if needed. * Works on JPEGS Only. * * @param byteArray the ByteArray representing the image * @param sizeClass the indented size class use case + * @param shouldRemoveMetadata whether to remove metadata before resampling * @return ByteArray the resampled, downscaled and rotation normalized image or the original image if there was no need for downscaling */ - fun resample(byteArray: ByteArray, sizeClass: ImageSizeClass): ByteArray { + fun resample(byteArray: ByteArray, sizeClass: ImageSizeClass, shouldRemoveMetadata: Boolean = false): ByteArray { + return if (shouldRemoveMetadata) { + removeMetadataAndResample(byteArray, sizeClass) + } else { + resample(byteArray, sizeClass) + } + } + + private fun resample(byteArray: ByteArray, sizeClass: ImageSizeClass): ByteArray { val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) val targetDimension = dimensionForSizeClass(sizeClass) if (shouldScale(bitmap, targetDimension)) { val exifInterface = ExifInterface(byteArray.inputStream()) - val size = scaledSizeForBitmap(bitmap, targetDimension) - val resizedImage = Bitmap - .createScaledBitmap(bitmap, size.first.toInt(), size.second.toInt(), true) - .rotateImageToNormalOrientation(exifInterface) - val output = ByteArrayOutputStream() - if (resizedImage.hasAlpha()) { - resizedImage.compress(Bitmap.CompressFormat.PNG, 0, output) - } else { - resizedImage.compress(Bitmap.CompressFormat.JPEG, compressionFactorForSizeClass(sizeClass), output) - } - return output.toByteArray() + return rewriteBitmap(scaleBitmap(bitmap, targetDimension, exifInterface), sizeClass) } return byteArray } + private fun removeMetadataAndResample(byteArray: ByteArray, sizeClass: ImageSizeClass): ByteArray { + val exifInterface = ExifInterface(byteArray.inputStream()) + val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size).removeExifMetadata(exifInterface) + val targetDimension = dimensionForSizeClass(sizeClass) + return if (shouldScale(bitmap, targetDimension)) { + rewriteBitmap(scaleBitmap(bitmap, targetDimension, exifInterface), sizeClass) + } else { + rewriteBitmap(bitmap, sizeClass) + } + } + + private fun rewriteBitmap( + scaledBitmap: Bitmap, + sizeClass: ImageSizeClass + ): ByteArray { + val output = ByteArrayOutputStream() + if (scaledBitmap.hasAlpha()) { + scaledBitmap.compress(Bitmap.CompressFormat.PNG, 0, output) + } else { + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, compressionFactorForSizeClass(sizeClass), output) + } + return output.toByteArray() + } + + private fun scaleBitmap( + bitmap: Bitmap, + targetDimension: Float, + exifInterface: ExifInterface + ): Bitmap { + val size = scaledSizeForBitmap(bitmap, targetDimension) + return Bitmap + .createScaledBitmap(bitmap, size.first.toInt(), size.second.toInt(), true) + .rotateImageToNormalOrientation(exifInterface) + } + // region Private // We will not require scaling if the image is within 30% of the target size @@ -130,11 +164,6 @@ object ImageUtil { // endregion } -/** - * Converts a ByteArray into a Bitmap - */ -fun ByteArray.toBitmap(): Bitmap? = BitmapFactory.decodeByteArray(this, 0, this.size) - /** * Converts a Uri in the formats [SCHEME_CONTENT] or [SCHEME_FILE] into a Bitmap */ @@ -149,31 +178,3 @@ fun Uri.toBitmap(context: Context): Bitmap? { * Checks whether it is the URI of the image */ fun Uri.isImage(context: Context): Boolean = isImageFile(this.getMimeType(context)) - -/** - * Rotates the image to its [ExifInterface.ORIENTATION_NORMAL] in case it's rotated with a different orientation than - * landscape or portrait See more about exif interface at: - * https://developer.android.com/reference/androidx/exifinterface/media/ExifInterface - * - * @param exif Exif interface for of the image to rotate - * @return Bitmap the rotated bitmap or the same in case there is no rotation performed - */ -@Suppress("MagicNumber", "TooGenericExceptionCaught") -fun Bitmap.rotateImageToNormalOrientation(exif: ExifInterface?): Bitmap { - val orientation = exif?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) - val matrix = Matrix() - when (orientation) { - ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f) - ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f) - ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f) - else -> return this - } - - return try { - val rotated = Bitmap.createBitmap(this, 0, 0, this.width, this.height, matrix, true) - this.recycle() - rotated - } catch (exception: Exception) { - this - } -} diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/image/AvatarPickerViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/image/AvatarPickerViewModelTest.kt index 058ca5c5102..c5fc365e5ae 100644 --- a/app/src/test/kotlin/com/wire/android/ui/userprofile/image/AvatarPickerViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/image/AvatarPickerViewModelTest.kt @@ -145,7 +145,7 @@ class AvatarPickerViewModelTest { .withSuccessfulInitialAvatarLoad() .arrange() - avatarPickerViewModel.updatePickedAvatarUri(arrangement.mockUri) + avatarPickerViewModel.updatePickedAvatarUri(arrangement.mockOriginalUri, arrangement.mockTargetUri) assertInstanceOf(AvatarPickerViewModel.PictureState.Picked::class.java, avatarPickerViewModel.pictureState) avatarPickerViewModel.loadInitialAvatarState() assertInstanceOf(AvatarPickerViewModel.PictureState.Initial::class.java, avatarPickerViewModel.pictureState) @@ -157,7 +157,7 @@ class AvatarPickerViewModelTest { .withNoInitialAvatar() .arrange() - avatarPickerViewModel.updatePickedAvatarUri(arrangement.mockUri) + avatarPickerViewModel.updatePickedAvatarUri(arrangement.mockOriginalUri, arrangement.mockTargetUri) assertInstanceOf(AvatarPickerViewModel.PictureState.Picked::class.java, avatarPickerViewModel.pictureState) avatarPickerViewModel.loadInitialAvatarState() assertInstanceOf(AvatarPickerViewModel.PictureState.Empty::class.java, avatarPickerViewModel.pictureState) @@ -195,7 +195,8 @@ class AvatarPickerViewModelTest { ) } - val mockUri = mockk() + val mockTargetUri = mockk() + val mockOriginalUri = mockk() init { MockKAnnotations.init(this, relaxUnitFun = true) @@ -206,16 +207,16 @@ class AvatarPickerViewModelTest { mockkStatic(Uri::class) mockkStatic(Uri::resampleImageAndCopyToTempPath) mockkStatic(Uri::toByteArray) - every { Uri.parse(any()) } returns mockUri + every { Uri.parse(any()) } returns mockTargetUri val fakeAvatarData = "some-dummy-avatar".toByteArray() val avatarPath = fakeKaliumFileSystem.selfUserAvatarPath() fakeKaliumFileSystem.sink(avatarPath).buffer().use { it.write(fakeAvatarData) } coEvery { getAvatarAsset(any()) } returns PublicAssetResult.Success(avatarPath) - coEvery { avatarImageManager.getWritableAvatarUri(any()) } returns mockUri - coEvery { avatarImageManager.getShareableTempAvatarUri(any()) } returns mockUri - coEvery { any().resampleImageAndCopyToTempPath(any(), any(), any(), any()) } returns 1L + coEvery { avatarImageManager.getWritableAvatarUri(any()) } returns mockTargetUri + coEvery { avatarImageManager.getShareableTempAvatarUri(any()) } returns mockTargetUri + coEvery { any().resampleImageAndCopyToTempPath(any(), any(), any(), eq(true), any()) } returns 1L coEvery { any().toByteArray(any(), any()) } returns ByteArray(5) every { userDataStore.avatarAssetId } returns flow { emit(avatarAssetId) } every { qualifiedIdMapper.fromStringToQualifiedID(any()) } returns QualifiedID("avatar-value", "avatar-domain") @@ -226,7 +227,7 @@ class AvatarPickerViewModelTest { fun withFailedInitialAvatarLoad(): Arrangement { val avatarAssetId = "avatar-value@avatar-domain" coEvery { getAvatarAsset(any()) } returns PublicAssetResult.Failure(Unknown(RuntimeException("some error")), false) - coEvery { avatarImageManager.getShareableTempAvatarUri(any()) } returns mockUri + coEvery { avatarImageManager.getShareableTempAvatarUri(any()) } returns mockTargetUri every { userDataStore.avatarAssetId } returns flow { emit(avatarAssetId) } every { qualifiedIdMapper.fromStringToQualifiedID(any()) } returns QualifiedID("avatar-value", "avatar-domain") @@ -234,7 +235,7 @@ class AvatarPickerViewModelTest { } fun withNoInitialAvatar(): Arrangement { - coEvery { avatarImageManager.getShareableTempAvatarUri(any()) } returns mockUri + coEvery { avatarImageManager.getShareableTempAvatarUri(any()) } returns mockTargetUri every { userDataStore.avatarAssetId } returns flow { emit(null) } return this diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt index 9ea1e9b9259..5c58262e8de 100644 --- a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt @@ -21,7 +21,6 @@ import com.wire.android.config.TestDispatcherProvider import com.wire.android.config.mockUri import com.wire.android.datastore.GlobalDataStore import com.wire.android.datastore.UserDataStore -import com.wire.android.di.AuthServerConfigProvider import com.wire.android.feature.AccountSwitchUseCase import com.wire.android.feature.analytics.AnonymousAnalyticsManager import com.wire.android.framework.TestTeam @@ -40,7 +39,6 @@ import com.wire.kalium.logic.feature.team.GetUpdatedSelfTeamUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.IsReadOnlyAccountUseCase import com.wire.kalium.logic.feature.user.ObserveValidAccountsUseCase -import com.wire.kalium.logic.feature.user.SelfServerConfigUseCase import com.wire.kalium.logic.feature.user.UpdateSelfAvailabilityStatusUseCase import com.wire.kalium.logic.functional.Either import io.mockk.MockKAnnotations @@ -76,12 +74,6 @@ class SelfUserProfileViewModelArrangement { @MockK lateinit var wireSessionImageLoader: WireSessionImageLoader - @MockK - lateinit var authServerConfigProvider: AuthServerConfigProvider - - @MockK - lateinit var selfServerLinks: SelfServerConfigUseCase - @MockK lateinit var otherAccountMapper: OtherAccountMapper @@ -121,8 +113,6 @@ class SelfUserProfileViewModelArrangement { observeLegalHoldStatusForSelfUser = observeLegalHoldStatusForSelfUser, dispatchers = TestDispatcherProvider(), wireSessionImageLoader = wireSessionImageLoader, - authServerConfigProvider = authServerConfigProvider, - selfServerLinks = selfServerLinks, otherAccountMapper = otherAccountMapper, observeEstablishedCalls = observeEstablishedCalls, accountSwitch = accountSwitch, diff --git a/app/src/test/kotlin/com/wire/android/util/ImageUtilTest.kt b/app/src/test/kotlin/com/wire/android/util/ImageUtilTest.kt new file mode 100644 index 00000000000..e0c63763c91 --- /dev/null +++ b/app/src/test/kotlin/com/wire/android/util/ImageUtilTest.kt @@ -0,0 +1,42 @@ +package com.wire.android.util + +import android.app.Application +import androidx.exifinterface.media.ExifInterface +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import java.io.File + +@RunWith(RobolectricTestRunner::class) +@Config(application = Application::class) +class ImageUtilTest { + + @Test + fun `given an image with exif metadata, when resampling and removal marked, then output should not contain metadata`() { + val originalImage = File(javaClass.getResource("/rich-exif-sample.jpg")!!.path) + + // when + val resampledImage = ImageUtil.resample(originalImage.readBytes(), ImageUtil.ImageSizeClass.Medium, shouldRemoveMetadata = true) + val exif = ExifInterface(resampledImage.inputStream()) + + // then + assertTrue(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) + assertTrue(exif.getAttribute(ExifInterface.TAG_DATETIME) == null) + } + + @Test + fun `given an image with exif metadata, when resampling and removal not marked, then output should contain metadata`() { + // given + val originalImage = File(javaClass.getResource("/rich-exif-sample.jpg")!!.path) + + // when + val resampledImage = ImageUtil.resample(originalImage.readBytes(), ImageUtil.ImageSizeClass.Medium, shouldRemoveMetadata = false) + val exif = ExifInterface(resampledImage.inputStream()) + + // then + assertTrue(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) != null) + assertTrue(exif.getAttribute(ExifInterface.TAG_DATETIME) != null) + } +} diff --git a/app/src/test/resources/rich-exif-sample.jpg b/app/src/test/resources/rich-exif-sample.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cbc95948cb75e12be56ffd06118dddf9b55967e2 GIT binary patch literal 159137 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

B7Mh@$gJu3p*%UUlm9IlejdRS)ZA^Zr&l(- z#Dwcq66z#c2YbI46ewEff-BrH+ejWK5ALZf_c`*$N8&k-3I;G{hdp(|q~+t=dB@#& z0V5C3Ih-X*qfb<-aARJ6G_4sdccE>n{*ltv3%N)>^`i5~t+$MZT{Fmel@y#ad06;P zJ59>_yeXLV{=M6qLY)+%fn>Y24XJ%5d|Og|D?)@(oB=n>{L~Rw&Ba`bURJ5~*W@qM zzV%k;``Ya172@LO!cz9k^2mSi-3As(Uy9aRZhsc$jy74e#^5oUQnx^|`11J$B&2Sk z@z%7w&s$p4WcSiWE7#7q`(JsIx=i)V5^v3u7x0D|kfqe3LnYw^Gh3)WD$t5UD_3*v zyJhbNeM|Hj(%j8QuT4U4CW%fdMWmBKNuKGIM9TIFQbR4AIu76$RZM7PO<&GK;X(DHYlitY(R9HsxxSb;%^|@O$xA&%Y=gOOzHbUh7pLY{8`iE1n#F}m*u@lbxJVq# zIc~EwGO4?HV+-@JWwdja?I_0dx3RG=ctn(JoKuXQvpWSwG=KAS?EiGDDs4z|E9#Y- zsvDPQr=c&Zw`Z_^J=3;}VHO4WVPEfitxu)>joGgXW}OOD%t}>UE}0!?Yz&()`YEz> zlPf)$zBy>KT#}E|*_DRj_3j5+YEz-b<4wNpt!>-AjGfs$4}v+%+s&O+$=wk5RnLBG z)=!z76MkBb1*>OwK37kxs|P$RS-UmWoGQZMf;SPv{cKh6rTUUFi>D2psZGUIvlXlL zc=e8z%QvWWJQmV#@!fhma>whaxbVFQ^D+&B9iKGTtAf=tFS;mUNW17;F5S_O7+Xgc zt|vSxvnih=S;tGFCnV%zMXC~2BNs0lLqci^F4Hxs0`Jma?_j;yprgAZ`-aQQ5AA)f zS&`(GF(Fm1Etlv#_jgsqx|7YGH~D?K6bKC(&O*wwpC{cuE$`ib!llpuy==pqzx$i= z&FNBxZOqp{sF@UJQioWv=j!U1#Tciea% zOyR{mnLR3UCw8C_Q;xR1`+S?XiI8T?;wgPvp|ofO4~6QeJ@NWj%Zq1rqb4)vs@%$H zyrq(yB=sfoJ-E8J#}YjieI}d;JzGjA$Gk2%XWwq=H=TIm%gd0ldV3Nx3e`^Cu)n)x z;8NK8ikFzTmLO|5q@L%frz0t>d#6YhSE`QAiq~r9Y2(>Ptad{tv`B(k#(EE@SUS3s zF)a#b@59Uan_NMKnJC?oI(VuNOO_qx{d)aytGJz$5iiU*%lHM7&fcTGr^Zv%jr%r5 z+npD;!aSx?wDAa+#)Bhe&HWRmXTAytOIMkmS+xYl}X>jOMQVGi&Ugmse1*CLmflW+9I20$KDr z?XeP*#R{IQS)UKaR!+X0(8Lm`<&HNql+2k}3~?JLObrRc7JMJ6?;>n(MzU+v&Xf=S zKLDISW4|&p(~wFvtz*VBj~BGwN?gGkBx-JLm0m?Lf>@&{^4*ToGlGiw+DXCoDdH6> z4t{4ese4K4YUx_Xhx|7-lr)KL?v<^c(&AfmF_yvwE&}aeFwC2a?Jl@+gOye4?mR`N z>e`IgHw`Qb_ff@g+jx>}M6$)VcpEkt&&|T+*lx%x+Zxf6ba%k3wYI z+}ssawQD%HiDpz}$tuSO0FY19gUvxCb8GexO%go7tdW^K#pP8-IV5lgJ%9Suh2k$3 z=;F@qz~0C9pEWInL*{ptn;u@=WE00EbYMqgQ*^cLRnIZjt#0M;o|&g<7N2OiHzpnS zw(l328%Ch+aKx$Ma!JNiWDE-9b?*)Me#62M>sNXu%o=B!(z+z}>9m2G%YQ(bxkhT)*T*6Be{ZTo>AvUW7=kRWnvXrH_E6_%yJiL z2g;>LS7;+{Zdy`4-^Jkwp-F zrGgeN2@Hd1Ts8>kIyP$v*vT!ax{qO1e=li}ZY}J&&N1H@ta)3t%S{#IiJy0t$ha9J zc0Zq5vvF+J@XQR*$s{tgPKe0Mz#N8FJBD{)jtBsPb3|a;(5`lhVbkR@p-hYdI#)@d z>rm-dHx|#Q+1#zetF6rN30mB$$Ogbj7(2l^CzVr!j2BK^vb#Yj^RIVvqtdN>JFED9 zNwq7vjoG@9l1G^m<~AE6jQMS!Ha(bjb}U68Vu#(yA`tR>Uz!0z9^en7LnUM)TUHdO~{+`&m*sVaw(#b9shF0Y-DQ0e}af1F!J?(HI|)n6UdGbHVFY{ zEZ_{5*mih;=JMubxD$ao!)I3F}+T9Y- z%W{bPe+gg@=~GE2%1YXwPx$-eUx2j_3EXM1HP*J5W0+t^w_KwuD0g)P0!LL=@6(F$ zuMzmi#=btb7FHJew7LY>9!<530qy~1K_HE!!Fil289rjig1`Vc_2*YasqguJUxC9; zn|~IG9p{C<9zmsOF<$ESIvKKf8fh<+c8vg*m*YZN6PnD@l`u_lj9iw<#{u33|F0@@X z>Pa-jwHF#x6K=b?jloH!jD^eOKlE@;{De3KJ-ONBMhVv!yt-+Z6?lp>ubsFV3O+8 zFoI`{7mIMiZEmZO066Q4(KC+LIe&b7=gK)%5eB#Q||r!)TI>c z*g{shcg5Ony`iz(S&(v;R4c?oS3v?y4P{KzV5QDGQP$S4omhG+bqEPb;#we-C)`Zlj^Y z;#+CuX>F_|w@aPjeDGk5lPe)vP=(s;&avdHbgVxXcys%2#djma){!h}JTSeToQ@LB zxQN8-EO9b}A{;X!p(Oxf#{-uW8GAclRQ~{__Z`$4br)uhZA-&2=+VQY+{$OwUfG&> zg{s8%@(GkYE)|{T+zL992@B=80b6PBbdllT7wM^@XbBDP_I94q={6U!O1DwY&OF3+ zUy=78n<*+HI^!vVZVO7KZJmruNnY|Wj_ zQOe5^b(l;r>l%j$21v==3J&uAD-RNQoay>dcd$pii&mCdF7*h~*oA0hj!+#<$w!pN z6~0k3M!=T}YX?r9D76l*_J5D+Re4TLH?rz(wx@lq_)6Z}N?9WCW{+~y-RchZ?*x{y zN{tIzu-&*UYR<^O1=vuoFo%Tk$HVE@;IE0UEcE*rA57Ge<{z@##PVFiG9B`VPnv;a~wf_Je z0;KKD3u~dz>Y5|#liaqOp_RV2hGRASw+khtt|Tf5X%*PE5DmECXBaqo@lP3PKV$KA zylAN^$rAY}uuBrQ$1xGH)Hhh!NayBKH#KEn4a+4cZLR+R%yesL)VDhi3x8>9dZpFo zgPU9Ac;41)Rf(PhJkhBEo>9T_fZEwtGBTZpP{F<})MoK6mj;a1wic0S%ewbe0wwbS zUob|cQAky1n263_G9AD;BC=C-=_R_i`~z-S&Q0v@c)TRtYf-I?1ia3$#SuG>P8E6e z=Dk8VKj9j%(zQDsc0E$$NQ~BPZMHxalpq5oit?bVg;GlF^GD1p+4HMQx8tUUX{g>y zzxkFQRfoj(li69@+vxB-69>13U@g=ZGCHJl77_f)gavREugbY<+P#R}X}9;@ZG=~n zP4?E37jniXh(=qWVi1u)43GZNC4Q}b+42q;S6oFidzyPWenhO;=+;;>f_GqL^RWjwITBRjU> z6R~lRyN5gyatbh~RWEhvroLa*jjK6ADJHeoL$n&kk)!FCu=saOmN+KUmOGo6R9rj~ zqwW*#Br>+r;eaZH;DLdF%?DSx)UNdVkt`2s1bZ!QnsA;`NYyriST6QZpzctl?ms9b zoh#IvqPeYgzxAn=D5X{MYPub+qpfLnHl7~7@V=!at8lUIeMEyGwGJVM>5O>xoFw9GB6SyiIBV&8+keDi#j0H zEp-O7wOMUso; zD%}*791wBO1awlBJVl-MZ9kRQ->>ztoTba}E%Y@!K4do*ziTloIQ_|xlp-~4hE-v} z=jAxuM%Ft{0ITh&yqeXWt)0Ep+FgT76}7dxD9lZ{Mi4K|*dqsNJF$!$8ugsH6k$zU znXCFg%l;gTHLRY;UEvQ8S&dHK9}z(`b6bab67wobVRiwugOi_rLW9=5ms9@2(Aky+ zg32psO5D#N2)uxba4@XIfr5Agcd4&u3k3*W`WzUEw(N|14;*NdUuJ)`7~+-VYjl(Z z453LRKHi`nI{-(^(A$&Z=AWl{hfx9i`&mJiRHol3qU_taWH$CugV-KN29&Wew=?c# z%Jw+#9sF&(zP`Dz@a&&s(%@o`cE9VTAc26Sji_^+^*HJ|73KP$jpfw!2N!oQDcG?` zy~!-AfT}QkcOS3RSDT5iDw1)F)AKF~Em>8(YbBIfv7S#lba>`m{Gm@pCp={C{{Sb6 z?fgNhJ4t;d>6TV}GH&KZImsiZ@pL_T^sY*3aD|)D=!{e7doPA{lQ)HItRBuQcaq?! zmE;POxnRo}VfR^!kTNmzU~*~NP28F_j;rBKPg0sITdYZ@OK~%zt*++ZwpK+xa5>5x zoNUWBUB|zJsOm%B-B{$Bvu7Wu_)|^sM~WiREoU!z4w@7DN;nH7_QT5%uqD_RRbr$t z48;TdyO7tOUU*J@4(sgkTUyI;XK@@_ZTwJ6F`i}l%4`hkeqwh97{(6=DtanXyXFNh zhn+}fT{_~{>dtn!^Nfimz_$x=@;#SrZ*6w6!k3J#9A-_K z3B7>MNeX&(><(*@)03Wt+aeS}qugBDzM!_(5+5m>Xk;;*hGu0Xfyx{xVnN(GDLo#m z;Y~1Uk9nruJ4P+zl5Z*}WsfMzb_QI4S_`QL@}$OLz%9DArA{q4x2Y19wLI!MO{B~O zYbNZW+=0djAoc!y5!;&U^j{0!YVlmZhoiijEk03k3eF>S; zMZRq|<}m~j>DNj2n5Ez&FPh<9+i4ia34AK@pfzrLNfzZRT|c~!$6gcC{5ci8nsj>R zobtZk?9UL{v^QmW{q|J^donhGxVIVTNL?*%4JXBVP}VFiCQFEHAc}oBj2Y%uRbvtP znT7!+Tjt2zS+PZ0YBf@e{=cgi&i$UK^UH!@)TRh>sJIeJ21o~k?Z>b+r496!x@xNk zBxju@SsgbzYz^{)dp7Km+=V@>mQu8cmdfN3MRBidtnkFmJ>|h+96+`UHg=5UpGvi? zSw#BAq`GiLwn?OAK_EL~--R8s)p`u$JPcBw#d6-1(mC5?mge^=pu9?7JAlAmdY{AJ zpcQPpT~51;G`xbi+l*rrNmzZZXf3)aP@!Zm*KZ(m&pki=b+h0r$RYTHrp)m)uLRy` zNZ77fdBEB-P5}T8;~W6hOGOLKtmSBL)q;SczUu4ysqx3k*cTSuuw z9oNn0NX$~<$;JzZRB~5n{{TqAQ-mYZVuC2Q`7xpy7`yFIH!{6?^3xcKZ~AJ zD-miw?Yn_xZ9S=;_%rfdaeZ1Eetis-Dp;IIcxE;~48B>%-1Q2qndVyX!4xOy{A5osu z#5yI`v7=ne94oDh_rfFOIiGLM6fv&%P*-F{upH-bw&kNkgH1TUk?7iO=Ch~CXQsun z>q!vCwz|akA82{NXn``EpxnYn0D?&4<*Ru$tc8dcX#en@piBABf#bjN=-V^vhG5fb5uR$3A3642x zb2!Q}UUxbuE?SCFWD|Jq!&&%iYh&VT2`=@$HfUDL=^JEEYOS=zAO{7+W@T0*DkNg0 zWbWj2i{WQ&r2MkBlc^I5TYI4E5KIX1I&T>}J?e08c z{w(pnnR$C0P(=;Bt7>-zqmomT53k#59vh3{tH-A5R`xy` zl-uc+acUcUx4u~=4lYWZ6@13*Rfa;7o=85X3a35WTJ`?C^pn~O@yybZ9(UZy(D@Lm> z#PUA#899#($+V70u4uS=8z$oUy-#`Yri*Q*Xm_94dY#d=y$vP2vY}IJ5GQEEe55XL ztVUTCK`yEY^Ph^^Y`1?AbjP1izJo`K!J?TGDGj2?$7-X<0SMD9jUNG+Vn3C)fHtWM z({WDTe={mlUez|Q{{ReaNd|?acrr^_b>y^#^qAf&ODn~Wtanh=5!=n z31x@MLZldWDBR53hU5W{mUjS027O{3F5^O(A-mIU{?8eEcy29!(k{$9b0CxE2=X+N z5sa&OU=S5o1sc!Vx>C~JN~uk9DaDxj&xt%abuH;lELye6^H^EV$f+PL!iHA`SxV#p znPH!l0gin5%2A}trrN+^hUH^fq>^VWb{virfyP)2hQMQ6%1~`IwlPjx-Hg2PE8NQw z5I~_H%rOncL0}b|lboHrp1nH(iEZq4jb`inLG11%)1Yng+%k~6ck}ZcWan|fIs3<; zCa|f?A9?Bj00;cptX$-|dL8Uu9Mo*>tzS{ome)*0^Jk7T3eSXP5=7 zoDv9DXp2AgZPd2QAd*&+Yv}F4SZ+M8H)(N@2P)k$#{RiD@ZKghxu*UP>-tB0VeHg< zniIzoOLKpvy|hjWMq#u-r)C_4ZUJltAh$W%M*s?chHmu;FXy&bg6eWl&s+eFsMB$s0jCiGQ0Vfb?YoyUzsZa9`Y5UdL$^e`I%QZf%i^fgEqK zWRQ^);E+RY#|lrV_OiK3rDqC_IeL13U-0HQhiG51ucT_0Wpz2Mt|R*++N|R8S&5P- z3h_p&y%_xAM{FlhKnsVec-G!;55aw;N{=*BxF+N(yz#p$29RX4VS@rPa8(aE&3n?a zjgjVE$=#k+sp|JFt=-J`kjm{LktMebiv3PhZ3R~>PB`bSP`cKnNm!(=&PZo)bDsYI zm$$dQc=cgPiH?~l@JYqE6OK)hjIyhr+9I zd#)l|1-O)3ypaz(VyW0zuyrG01%CE;Bb;E7?)KUr*nS^Lb$2W}ychev)e~EblnBmM zqreIbXoZ&q05EtTcD+0WqOIm~ViQc~bx()-X1C!BbQ*k7sVAKj$gJ~Pl1OaMW!%Rq3l2doo0_kK zEiXPA9Rpgki%Zt+p_cO2_U3q$Zvl*q9P+EWF}wgU3X`4yeYe&m3a9ofpEIM~M6*roxfPmKK){46O17W|aBDCoEHO91-QJkTPwwbX9lx{afr2Qf;FFj2weA8s;yG z=sJ<*YAo$M88EWb^}Rw!WofM--)#2oRz;PFNXR3wc0%1a7|9G(uw*R?Ih|Zwsr}$? z3Y-zh&j4f7wM|6sI+~?pJ5Mj<&n!p;vOmhh_;J_hGDqYqPfXLr&CSeLZyGFnBO8Mk z%MgBCa84A1yCVeP9x1y;>JG`=)$tacrs=mRZ4Jo=B`&j0AIg|94x?%yz`)1L(Eb5D z0|MN9NYh+erO%Q~q84JJM^IWd$mm*4fO?WEZf)Fp3ZA>4dybVL>0aABlye?1v6c%+ z{6(bN8TFkT_sbkAe9Nc?eA|DOOE%;zQ92n`GJ00FgW@Y65!#ELC|$jyMv_N7_L9Jo zt7L+nVyR()oQ^O@Jka(lp|(2Lk9eoV8iu#=r$+Fkd!f?o?xUJ`mG+yq2qqh|)f5Br zjxr8%cHqj)0I;g`bOi@zV<%W?gjBSpN?-Qc*67Ai~&EI}q@^W>0)EI|Pd7w(Mc z(Wg%CI$i$&!A4YqvJ5-$&KeOIBOmX1u`I0t?=2j(SA7+%QkPwX> zxzk8$(x+>kf57jEo*KFEJolRQ{4scz#UxA1oBOw#>gE{Ne)Tf5KHLIIA=(%*3cIme zpAkF*Xy3DZ7Fv;s^66~!EOXdNB4%Y}l&jTTdJ4Gb<H#630~2AUbZ791r%}+t0GOwvuxTJW&)O;)+j`D!>7?a1Ryq_2uj~ixHTR zWp#}d0>zn+000T~B=i6hcm&pQy^~|Al9RE4f8q#qStmN%3uv3>x0Y5&;&ZfSF(WV= zvz%_*obU)%BBOZULo-G8d)RI)-K369LJ1Y^ppg|=$0UH^7^@Z;T*|S3D zP0+kgZ)bCLZDFaw88TbQ%Ce^*fSD)NRHw z?TR^_w;kF(b=P$rL&p9tv4g?3`jEEKyxS>krdVZ)St3V{ZMO0<#-&8DqX{-M3HS@J zhUPp)so#8QU4QKQVyfGPpH75WI=PBA0VMO$+jPIaDp8YQ87jjKyD8r8-A;;YOZGf{ zG}gLF=7Q$gY@m)A9tq?tB#hWrDTT)bgNDJ{#EsiptHq|@oi6tAtnM1##X*{Aq4QOD z5wJ1N04xB>J@^Fhd>XXk>c_uT+qu`PcQw2ZqmL5nb~;OHa#$tHGDk6t$cpX4242c! zDx~(_57)BM?LHuQhg;QQvGES2C;TIh2pY8SX(P)bv>_lKV~ze`c(`D_Im>OX+!P$? zzFj4%{{SNyG@9nv#?$ORBx^ntmhVf`b(hgBE!rE&T3H}RfNud*Wm&=!=W1@j&fqhH zZa*69z8})G>HJUOyZChSBU?MW)VZGBAd)3tE;#0pp${6lc=sr9#C*6?y)@%eF!#SQ zsyI1&wmjP7OflUH>syO^t7#5E+#d=sKyiRG&IreDfMULq_%p59=(@+-?PHqZ^~nTr zTidIuTkdkWRr$j1@}LaTH{Ap0E`&*hD#lVtX|1*Smnw2mYkd!0)I39Rtb8ZEyAfMy z(aCTuCtHG8GaIG1W_Mt_M+_J;?iu89hV!3`T4YmrKHdup3;jn?j6(iSr#SO%W1U$} z6wChr9}6dtSm2Z9e-gVbbbC%{x3-;o>SGvk$y--{^4Qw=E8s`H@crJg;wx=BOC4U> zR@wntA1ch! z0hY?0tG&Sk<>Q_-Ekc}I_+9Vcf5F_j*KV}kw>(nvOT8A{taHwi+`_oW%b4Vh9><{# z_=8$L6!2EFuikln71Ezvy%yV~bqtZ&vxX{5u~k;UC30|Bah!8WIq15Q?H8{?!uTV_ z7tvbY#dB>M-NU!d7aLHai!4l-2tbU2_+x-h{2bNR_*<%r?UU=8eD^9&>tH8I$5EY= zM;-v%$znEuNyizAlZ7j(bn-QgIY}uezxDbX_8tiE<(9FcUD&RxtlYzYX>oaDEr@4p za?2wRC3l7lgfAzOKs$FX?#Es5Z-=!lI_Aqp(5>z7-f;H!656aXtdcR4%VLd#^5Qj* z%21??735ZU3{<428mnh*J6l3=6x1aiO}{fL(^HoBUC^{o614D}TfZmms;lP28aIwa zX$iwd9IFy@!fpG#C)aJ^d%Z^1>rIDDg%|9>MbVe+;h10@rG#is*9(GKHh&ITxbrE> zts6mW7r$nnpVyJoho#FcUj09>>r)$4*0o(?%}%A|%RRNV#B$2ZhIDO`5~C!EH$Z@r zPD6qGEKV2Mjhfor%WP!|jUz9dyJSUZ_c8gD^32>0an4Q&uP&O62s#xWmu)}2+vKnE zHI?NqW!Q?!@!-3a^!XmvS)4dPcKJZzfL6{xI3ur2f==eGdju({$*RvfENdgQk*-D< zE>s1_0G+Ba)7T8s#M6vol3VEi01wo0l%=5i%ln(8NUUMqDQ7_w&S+r&sxt`e& zd4PsstO?HyfSmOt91MYH`%%^#eF$oE2(K;2nH{^CFpaP{+S%Eit&pRrCm4L5x|JRt zr?;V}ow{39Z&Y)}Dx&46ShQQV@U5<$6l?vNeJeXn4Z&HkvM^vW2~`1p<9xYAAU8SV zbsai=BEl%4xw$t5n>aus@`oA7bCbJpAdlg$tQKwcy6%*ftgrVo97Y;6qjdE!ZeX~c z>gM1G_QnLQwovQyjlgFooF0Va9D~mkml}Pq}d}Qd2(&t^9Am_XOesHeKL03*JGrznrl5#87^hg)#gZ~ zOMIYJ1YiNc=mT@^PdKjQM)<#bq+SRdB&A~V1*<02KrlE2`T$7kaz;Cjs&bVy<=E$_ zl(as9@YVgEw*l4dCuv?pSl%u|z;>$?0|TBx$rU%CQ;fTq>G&~EmB*Y*BK&c7IbBk9*(HoQ7aeq{EBD2%sIbY(19 zF5~jx>>RHrJm3x7CX?|>_ezgexVDZTGU7GbjV4hP3=Au0aR+Cw%B|{1;+=jVdG#y1CA)D-@3w` zl=M5F5@}Fsx>c8nECyrLpxJT}NfF&dFx$vL13G;DOEU=xt{?{Ul}`pvQX7d+HL^vOQx`7bsy!V7B{1ysiQb!<+y)J3u+W;PKA{Q~U!R%WE2x_ERd) zWv5GYqC_mR7_lbOK;Znj2P2i>*70r>+`U-oB;Aj`g*;chc;kg~4QzY|1h+bP58<;j zNpWVx53uiHmx$v6AjO&0$Ox>=7%BuN`V^7a+fNnMyi?mslR+v6j!4)tx{;7U91ur( ziS(^LZBi{pTN_u@B2AIqG%969=j9KKf%7*R?tN-nzKm+<`TI<~KM8(2-`LN#9RpWv zg;ffv6Ov+OUEL#M$WNeh2SHxr<9`poio6G_=+H7O_izyrk;L0oVt)z|ot-D<& z${2v!1{3Bx!RH6y-l^gJ9)A$&UNE}7(=GKFg{GlrJ-f$j5dP%((r0{bUO)+Lin)BZ z%%KFV;@?l{9WqOnN;=AJn-I=nV*YaC+Q%IZW(ZY3ZWSip960kW%= zJGZAf4Uc&6-^CqTe-*xwuG(t4)|YV{R=0X#vWoUd)yXHXr`}4B zJXE_KhlqSzed7%_&q&qVOno-y2e*ts4&x=z4~keXPT=@2vFj@LJ1nBd87mF{{TTV6ajSSLPrByrlNmxK-!x&!>ZQ;D}oQvU^ zZ!bJU4Cm~Zc5aZhoRaQYVDm@b=0J9X#_0=u#hBpuKVOP36`u`jI&@IkYmGImvZPVR zDmjyD`9eiff!P!|Y{*_emjzJ=JgPX9gY&` zQ_^FN5@Q#Zgxa_UH308Y00(j*D5?`V9v0PXyft@c9M`s2dIqs!0lktah1_vU$IV#o zA}mn4I&2^ie6YoKvt@a46+N9-QrgDX^y~e9kT2n`%+l54@c#gW^m}g<%X@97c&uvA zJUR?fTeNR#?z zGO~h^NL-zu5xA%if(8M`@c#gUyj`eVEvBKbYnpYgnR90cn;pP-;&!Tq8ed;vGZe;!3?ajna(-Cn>iCcBa&8#A-8A+qdC ze7Pi?o!vcp{{Vy@67cVh^nFg-#1{Ieonb6;t@VSpZPfArQ}VH5r)U7lox4?rcFfhP zZE;g~jn8q=bRQM`4wm-QPSb8YVc^TDg}_^2ziYF7>ZOceWyu&nAi*(~K3-0E4~ZWV zd>Qc}p@&HD--_-$Lo%yF1W>3mpjKgs(78FlJHq9BwQfRfE_76&;r$|GIdl6&U$OC@ zkKnP=JT>622i*8-2yM)d9o&mG-<23uGWl|<6$h&yEbn$WJF*Xu`1f4#P5%Iiw5?Ob z`lY?>_nK64Tgh&vBWPH%lwhi-B>928bHUq+sZ(%=BKT7Nzd>lrve_Oe7Jayh`j#VM$}?y*dB@7~tTV^q&am`nQ8LYamwg+HG#lF6Mh#C4x(IcPk4s z9L5H~?YkS-dxB#C7s8WKcW-a_V@C%oq_^1Ehs5*hJ~D#l_i&Z3eAT&@b03%HDnfEF z@$T5iaY(}uIR!zEy$4vg(zP!J>Qd{oKq8SOhSt{dQ5^8g7*m4FhuF@$iUEcyhXApq zN>Zs-qNu&qYks4sxhcWH>hFKh^Bo^hveT{7RJ}>HJ9}PUX$%6I*SFfl{{Yx}R@;j-B`soxPd!HChDBcJ8;p{0qpnSDEuO4=CvRzQ zarVg7fgfkyn?Z{)6VrC(k%n89QH-7d^Qza43As0AC$HlF028;^$wYxPeIow=%+a+N zA5Tq?JTCG{y_KUSRwBw2WzOOb1G^v{Fmof-Ro6?t(_$|srDD)sTW)k^ot;9sXts}= zhQ>+faOC{H8kF5hMzXwJo!?8v-+x4{-QTIC6l1sbsj056jXj2;aF+45rS`c5sTI!F zDAE?mV%u^1umQpO_TheDYnMhgnvUH@-O|$SB2-(A(Hx(cFeD!9jr@Y7v0RK-rj zs#A_4?WsOpI$2xu`}>@;loZ!5Qq`sFUduW(o@_8%k_&AcZ~)4ZM>~{r$;UNj=Ip13 ztsr>*M=iLCt&EbF^CXVmK!J+nf`kl=WQ_FSXCUBRT2#|~ z+FO6?y@aInk}OebrdB_~pmG-gVR7sI8*dN^6j9G;vn|WwNj$jX zO~h^mfx%LX(2j5h8GsebUR9MiM_XO_Cw`l|_Vo&Unzq%~PNtrXs_4+)TAS@9uhtnE zr(d;5fzHAFYB~Fhg##Zc?7-JM;~jU-)NWSNNp0b2!>zeuNK=*yTaFZ+pmaTbE4L`E zNyc2(_K#<8v!0FzT`Hs;`d?{ULzMG5=D zdk_cs)LAWxOH)e8o(R>Vk)rc(v6P57Vlu;U?g`^3x2ObG-L=%KZ34VFC=+@tSYR>h z)N#*G!lp?*2HlOFKIT!U2rVW^?`KmKlKGoLqxD{x7&*>*{WG3?28!+0&E*Xj%-iF~ z%kT4KW99r$Vg+vbWgb)W`<$T&DB6~Sru1K{tAA<*?JiRAEJ zxiLC@?CJv8T({nVh{CEi3oZhIlgA9y?DdG^w~Ib}`2PUlzxYLbe|2+f1-u$=rcCf8 zBz&g>aNGPjY@b7uj)uI)LenF@)h*z;c@`N+<{hMroC0#dD9>|@b?;nNA$q!-L*Og@ zl*J5~x`;88VMS&q10c5p1as@&s!oF5G*wVMz(FY6i5ztR;BpQ>{Z)hP-o>JU{nNUF zQTjJfPt;R4mB|DUa!%o#)U?Zvx--&z3w*b}5%Cs-e5!5Ww|z?9XM7lBiCb@%oQ$z; zha8a2-xa_}V|_uA+?Td*?~SBnWcNME9^(Xo(wtSK{{XKq(CD729*6M*Qq!c9!j?W9 zjh9Kb)82Rtw&ppDl2!c7ah$`+9B$m%`H8PO)Jj}lsz6v72mngiDcAyV2jhWRrrzaA zdtwvq_mBA~iPi}IErzA!PuE)UkP_Ksd>+RFWdhHFgCf;qOl2ZH8(+6@e zOrC@?AmkDcRNpN!ptXKS-h+yGuW<3lflmXqB8P>ig`_lbUlKka!{g5mtO3M2m8R#j zmHy`922uDwD_~@Sk$`#R9+Shbs7v9UIO}mddZNIqbgodJFbpyI30_FZ$6`8kthp^w z$#q3v-LY--yBReUzLLt_uajt7hKWpzv^ZQgGr*QmJp_;a=7WGgFI9d_Y0B)0sncGCChS@k}d_@!lM;xB@pBa$%<)K8_o&Ah8F6}EvY zFk#hligD4ggTb$Zbd6g3LGct?mXCP$_tw&83#gQE<2!gDBT%XtV>?(Y6IxS-y#0TzNv|ZW6fcB4 z6?&c%wz^i+?e$AWEv4$3ee8zb>LxMDfwmu>86SRB@G^Ffm>THy4-M%aJH9WhS;=Lt z*hJRyv=S>7jmIk6mu%5F-;K)3Ky@KWktmHn-SWwiI={T;kM-nvt=5-y;|)fA8V?RU zdM=pHa}2s|%f%$Z<1gnyI}(n>t_JO@M3^8bEWGZOW3TuY>|6MoL)7Hd^vlRUvULks zQd@abFAzqMkO=_-Lkpj?*8xVv7&L-9e=Ot$1Cwq#F2Q0?%i$*{{U#j z@Ah=Jg;pDQK_J8#U*^N1BYG^HxhWXhwVmPL3+lcu@b0I4kjU1)AGB+!XO>;5Ho~4% z3;+z6rkfxLMvk#SxV{L@=5%ArrmgDd@&RE0>>*8x|7vYwl zeXD9VciNrBtk(CdcXb*x5TKb=Vt}^hk6`%#9FH^>J4WQm@SV?tb>Hmo0Nv=<5FItH zp4R&EWind%lM*fBkmf{_<^yRV)5{oQ${L+o@Pl04O)1lzNI6GKekH$)H-01V?Albi zcZpgJV%;{|y2gt(u^#1CjK}vRnK$KBCK$}y5Ea4sXUA=G4y6UmI<4i{P`2T&BoV#8 z8@z!KcJh%ol?(TBue7kvGOX<@C!ti-eU__I*MY4gw4NOIWX>xLIWi;^`6@3w*Kw=O7Ul2>Y!X@{h}H zX_mj)w?Ah1gRIL1hMnf9hS61?X4&#EbU;Ytuq?Yy21Z94(WNEKrtYu!Tl~yx)PqUE zYj5lDG6jSqRn#Q_*%mINS@zL zlgrd$3eIm5NH(!nEUW}MJ!{<%CQG&QZ}DIGNVmJ%bLkOmse$P>&))L&QoeLUV6Qi_5A+;LYgesI!d*S zQox#IB|)^DxMl@7RZOcc{{SV%2+QsOl2)6fMHR-JHLD0Aur{+faT2YlXSqrdq-_;K2etk$=ydueqQ#7=if84O+`+g*}Gah3$P1mrf> z>x`N=UvIq87RBT@3ji`kc7~EHotsyjlk%a!=m(%RD+Nq6{xNJDU5{%)3 zw@eHL=C#G`&X-&3)1|+zrN?I>YdsrfFEJTW9!0(d@I7Dn>fxkWkKyCi(L#&g-D6sPrg(4(Z3r1sl( zt$j4QzvN*_CsoQ@G*i?3IMG?!C9B@G$}`MSVaCQ0H_zts+<>8WpS)M)2N~wPPvPg( zbjy8i$)lfQ1i#o<zZT3{tK#C~W=L#lbNprW6$3dUVBbu6?Lrl=M zxfYhep}0hl0!$}7oxRkIWP+zBzIj~W-G}W_x7lJ>ig^O7O)P8!ZY5Z&WQJTe3k-3P zdUKknqbqg(o`B<}&3N>;u(7ool+s$WTo!kqI|4|mz~Ofn7>$V`afXi_vYJMlE#AD) z6%fbrhKR&VyOK(bHzPSw>@a!c*7TMrd2QeYmYuVQyRlgA$lq>okq z0EENgxLrHNw(|MX!5oGtbtT-c@kxRJEL0QGSpDw1mnPri4}yGs;k`pb@qL}%mpO>V zs9N4dk0JM>Wl7{PcjGDc6k+=iGfRw@1xhBD|MV@mtzxn$znMX^Jg$ zvvq7Ua}EyxFy=+ds03w#jkq8K&TE>Dwe7q=VTxE}JIjrTssU`OuFyTnJu-L;--4xf zeGtppbZQIYpTNejs!Fl>yTfRC5~gX>sE-AZcf zLi5^L>QT*ec@Tl$E#_MU3eH#pHj*$GB>bz+F~IqR2ToaZ+so_CI!Gb1b%jJQIwF^1 zLYF9VKqUO^7>%PmS5l;&#Hlr<%?%=ZJsaXO2qsCi_LRn5&ju$LtF22ehv_N)?mzdzK`yIa%rCQV7BmuG144b4y;wNAjkW{?LGIDS-1y&}`o}je3^zNaq-fPy02Y_xMo^P~A z=W1sUYYHm{bTa9Ow*ubSiII>D-JjeOb$nX zlBQSXb~U4AQ62!&KrO$gsCYL__|5Qx!%}#Z{{Y0vt)RFXgfZ-rIS4~Gz&HW+k+kK0 z>A@;1kob3`+UUL^*ZvcD=38Ax)Y(G%Vq9B@zS9#2l3<6cZFha79p5B`U@ik`G@Jge zRqUk&)AT2~@iv+8?@7M!Z^btL)zVf-e!(oTiSumiz&nnhM#-FV;P8Newd8tlfuBM6 z?IwjbjUJ_=!EptR?AMRFD3;@Dth?7>Nfrb!{WDsyjeeqyZgl2|(J+^kbBzbVn$$|-67L`E%1&A;jY z09P_}KN0Gl9=lyWY4sZ&M@G|@*3(A0l2nfBW)du5Zg1X3WI^{)N-e;{Y>5cW(zR<1 zPgj#u)^D}_A6v7yGEJph-^f-ciw0SdWQl_O*AnJ7X4o=Vh~3^e=S$p^+|k1Co-yO~uAHJJ(_^~1L2nE$QZaCe)%?UFW3^Y2^KE7vR+I5B#oCRg zfnlgmD*dYLOmzbgRFcg^oAWA+ETF{O3vENOyv@tFhNm?&t^IWW0D?!di;JEA0It9A zK1ZQy{u}!~fcCy#n`@>|Z!h+qsi{NeO(7Bai6Dmu%#{p5^ET&IRXdB9#UBTFiN>94 zec-Jt?0y`!vTLi|COq6pEQ=E(Dl&${k1i4v;or>MR53ZZTbXw<=*-^>cA&r z<8L}X*Fzhx-{Af6!NJ20sx>a`^s#fLX!9OvzD$s3aI*z3`*zV#pKoP~$ z#T~n}c-xknj=O;5Vih&un!m&QIeb?(mEeo^Xssa^Grje~xP^S?Mq?@{1rNokJBgY&^8WxY%*x6@`MKu=WH2g7I6h?T`WI!;`itU6 ziDI(w{X;*@`*C`(-{m?Ex{WbDz4wcs>vox?Oge`iD7f%9WAwc2;_?4 z9vK}jCX;o-s|c8*BP)>2IX`f=v8(;h?dH6 zrNoyu@=a*g_j*>RzEN)vmLu{am75B3zF>EZ;E4MM736+1zfEIay1IF!cA1_)w-`HB ziIb|3GD~hC00Ae3`=bg~+^tRSo4@t_XlYt2;*QMgybl(MCX;pjx2MSe0BbSF97`aC zbCHnl2g^*OGH`>Jg_zxW@o z{2rt>rq-5U*Y)U4s70yVX{{{Q^2=z=AdrY;c#)SOfl@wM0E9cfIRzQ>^JCI{9erVY zW2imsklg6=#{^N>%WRU|tj!#WBSw(0C3uJ^4I})#vK_=@MXGXAsVyC~efHPS^)994 ztre+y_S)A&@$@rWUIx0mwfkkI;Js3(QJZ9$A=WS$@FQF)j{x*xQSJbymX z!Q}nX-#vK*Q5?RFI?Z3=wvW}j-_K9D=ABnnIZL4VyvNb>`+K{G4`~^S+j@E4b8l#! zNy@ZnSPUU8%QFC>ww>)YY%D_G>K3fC&nEjT`6Uww*|$W7V8G=D*$zO@$lMS>UrsJE zmn^$KO}hMyCCs4tf2~Tly0d>{UP);*cF};4q;pD>G^|r4+k3Mtg@{lv#d%^QAexdm zEp+Q!88rl$TR{7k>&eZ!Z7aLWbtK3Oe88TJ4j1od&BfvCICIf=lfH|+zXa{Sc0?y_ zBwc&SujR47xmX4D*)jc!H`>GGW9G1pNFcMUgnXz(%aEag9PXv8OJyCzOlP;(qLF8l zdS)3m{_wIDa1gL0oG&9IkCZQqhRf8kKE_d8(RcMqOPlj{ziW3orBd)uqx{Y?_gcTy z5;eG%CW<4(QN*#R=QsgC>7G3^flYbj@>UrXlpyU;#E$%)Paro=IL3OCe!krfCnnw5 zS#B9OG{JVrnApIOK^z`CjOXjtu0e1NMC67lL~1$n7@mN1$4&-&{tK1ZOHB=(B0Ia= z>whNRLiaKSiYQSTo?W0G3gZNxK^Sbd2i_yF)1$hK*m*XpqG#s(cY zUIz8R##r!UtxYAp`+7g}{QH{IyG-iI`%antn=CIo2uz7~j>uCn6n~i$B{+1!+xMIO zXUtYDBFURyy}rL->SK7rT;(KE3ZXk#V6az43Z(tz$3g}Avj%_|WnD)GEireCF zSFR2Tz`*tIiuznUlhN+Z3h;L<>610>fSI9|ADz`!QG!%40kP;n1b4x!!Yo#ySC%LE8yK@Msuf)O6vVTv=hyaR+1-SIRs@{ zz&U0t;CgL9NCdEas!`midy=`#T->zR^P70q%14vuhysBLJvx(sagR(=B!^s*=3Q#_ zlK2e8JFZe@>Q%_(G08iQa@pO~u;7|!R2SH0WY&_wIgV8^vh~l?gY@_5OtP`LznDb? zC8I1Cd0>bM1dOpb94S1J(A22hTW)s;!8cH8ZKzu4S69{#YO}Z6%yE@mnB8&;ZJpJY$fA4b91$0uLk-(YVSZM^fVkR8&s_F$sKwY?%T~^6 zR$~B1Aub|f8CEqJT;v_Y?;Mgj=m5dT15$XOQq`^QmBf|gCoa?ejmZ~Qj*S5F~dBS*fDV2jzA zr}94_4L;Xj)Mk$5rSO)wZed->vdd;n#|MSmaCzhSusQ2q-iMcCpn=S`@yPK!F6tP? zAq9|;_f82J9XKR=nwV6Dg{7teP9F4ziAOcz-yD1ut@yK7(AUHITqY0%H`u~E5qXxHF@$(}Ga#uNWz&7OpE0OSLgEWs3>y{eT--cw;?{x1i zwE3KwP$j-#@+3k44;WO#Io?hgNb>fsZQ5zC>ffonCiy)NF4mt_@R#i=3A;#itv^xL zZ7%1MB7V;|%ZZQv=(Ov-M@(N4THM=dn$DWYnq|63$!slTaO!NQRcT}qlPjna zN#%x&08}t)>wXT{!4Jc9)~~cf2BBnUwSvnnv2h?iTnjU^A!!r?mnEEXaHtbz-!_I% zMC^KQyWt%p#X1<%yg_J}uwaqqj#bDY$i0>MI3tmg2L#q7^xC(FJX3tSCWT;hS?!YM zTZgz7kwrG^hn`soMkJrywPRmuBteW$w#{q?)N}!ZiN0V#A1OTN2D}qi zo^J^FqD$RxGFU#Zc_qwN*AEo?o!9L09Bf!6+vYa*We*nMSSTtYthr-!ul;|JQdf37 z=fzt7spDUTizK(u3q2=LF{rt{MwRzQ`TOOjA=sana0`rJqUJw0@SUXaYS7CV46-V5C%wEnjpD~GymxG` z4A9195EZ}(OPOL%nSNW5fE|Mj`}<{`Z@e#ib8Qx(p+yy%+`Ox5CI%TctWNn*&9)J< za+gTQ&9#cgR;Nz2r!P+~{{ZH$Dsz=9!YlgQ{t3qEdMa9YzBSi${{RrpYTjHELnWTu ziJgYf+nAk>9b>=;q_`{x2h99Yp{|FjYBmzhBsz>U{hs36F6vHsJD4t4a*Pv=shr%LV4kc8QoqlB(`gxD^UWQ;prX z9tQ_%TP;q;_hj&)k}0I}TGmF7%}vazLNX2LSzUq8Bmh90VwBAEOF8!hMo8(Y(-*XR~FVD(!e7_HP`^LWy5`8^wq`A^GyN0!z z)q{CJy8$7OJ4!j&1Fm$#2i8vBeY*~^jj)qW>ct$3HiekAbjj;p5X z(`yjNB$|cYyE92|21#|6S7b?ELA3$jxR;Y^q1hX9vg#>esOq|6LG~S1TZWc7B`Xc( zvl6L~nVcY38_3BxM*Zl>2V74rN@}!PxApj;;nb9!IU}voug#8wJ&YQ1T(!lFqV9W( zmCDB1{{TiuA=HLava-4Wzv~Gh)TXwTHk;v{D^<|siaB&_t4`7X0IHE#2T~P@P(Vit zP^kGMC6}7XJH(!5cH3P=U9w;(wRp-&u!JdUWkO`f-Rq-w}au&l|aOB#tJfEL{vG3bg_L%zi) z=PF3tz;e@#WsRvDUJ_Sd-DxYW{Qm$CQfE_=DW|XX`JINPXB1Ls8Z&AaZ=_vXtP`|Q zIC$49=7eWPBtF=hHBhCI(L!axY=kc*oENdhHL1HYM+|OOIb>p+k#LSkKfLO4cSd>+ zPgXLVs7<*)b*m@V=_UGGroSR6KWP?|w_oezV{1BGaG9nyvD;otaMq~|(5i8I?#yrVF2am)ox7WYrzL)2%2i=2;v8if(f6Y5 ze*JVrG^4W}V_<9+*7`M)Hn*Iv=z>b{$gCK;WP(5?FiT_jasl02m)DwIrnPTzZxj%$ zmf@j-eX3jchda@6w1TmoGD$`QutzyMmAP;3%Krc-Yv1loY2RUTUN?_V>+>yJ$$fRG z+%2)wytH?Y-q5nl8U|btBo62ZA1dUKNdET?2C1gqL1J$-hmzT(x02rD`#M~W(67rk zA1>v{3{DD$&IV3AO4SuA)^5$-{kn8(Ze?wCw_`}bN(%bF>;3^e7fok(9qqNmcOEA) zDwtj+Rh;nBWT<63i)3V>`BAW23Y@2m^!XNXaWsEtLLM^&jk;82U_txDk%JoLnT`&5 z0Zna}VxX(5b@x^O0F_$L@49-gJF;f^skHw9rej=L%N3L#XpVP>cb5wi(e4a41x0nv z6p~H><^A$;Sd}Xu$V{!YJ1M{(x$A-cb6--UbB(4&GD^7L6ZwY(9l?pn2aFHXsOi@= zb51hvjiP7s_Z0}fnB%7>zIdvsq*!%2D;9%D`${dqjZ$AWV>9_|Wn8ZTnB;uj6dW!{ z4UT%PHc9X1y)#LD4U|q0PGV~02NA0(%_I0>yfmEQEm~{u+qL`ty-Sl))6mYg*H4)YjU%O`n3=`97+*VC znCFe7Ib85KZMobS^d24Yo~HztHug6*KW3Puvf3;Nir^q;>;~*0LNUlEsu8a{{W0_?`$-IsTl3AwMBM@-CPm12P7X%VB@dN(*T<2>@`b0 zQaK*N;yKngD8&wNf5W&v$4dI>yTv}|owKnu)w*5T&2K4nNsyx$*sB0WI3tjK>%y;m z2{w|@QOM)>ZcT9znWb-3H ztS~@1k$zS8S{n;LOX{o)d80U&PBRnsel2G;_^%y*i9>X~q2UkSW zH5u)$X4BOfAVSkIkri4#j5%P$kU$EG&fM(;f-8~>ncBwH68Mj2gId(Q6%DSe`p%zu z;oF-~?{lTvyjR+r`RYw{Npsp!-D6Cqtd8Ph1R@UI#&(b;dO1yZ*LDvsbb*H3qlT z^=pYOb_fy4gzN}d1Iz6l$;rUZGl9Su!@b)Mqavwpg`E{Pge0DWw{9xw-W zhujfhfZG&h@tz1Rlg-Wh*Bf@HWA5=!73;IhL&ZCaMF97uG+Zn>MruHuQe!|A&_Shf z*l1kS_WIKh@R_R8K?a+r%l2z_MJ~`5R2jFw_aJYPla zMdmi*=CyBInQ~}vYTpzsE$kzgK(t+2Q#2~z&-QqhIRu4dGP>;m`Fv*pmP{X+bLV=; zjP5=qUg#PYn)eo(cZndqh{rT)CSehe%bAxT040WO#vGQ9aq8*>xh41nVE1N^!T$j5 zJsVWG?Z+tIqL3?U&5EG{gG;(FP?PqW`Q@^+T!V=+|A{~ zBdRtEVUkdaTM`u~EEu^Q?~boDtw%}H{B0errP;K*fv#>XB`fyW8qwMqiZDA2vrhzN zaQ^@#in3&S0BQ2(1VPQy!+QIXcZXD{pi81H-=9sY~qb+Xnps4NlD@1<+WKF>4^5hU3gltpH7<*P}$HHr5FkKKmj zd_$?|Phu>fmiiAqDN=hYsEgh-f<|Te-Gc6SEg>LCrbYyXAxZRT&)DJOq^*1O>8+Qo zt@xahrt4L1JuUruT;gr?y>G+pFSNL2xw_C-`$GIIQ^e4K?7LShw|2VZ z;k_eK)O;PR>XB>k*j(SqZzcFot4TmnFk)JC;M?VyQX5V(D5Oy2*Kby)E50 zvDrDbCnYYo`Sv{bP_VnSxY9Kp1s%0nU8c816Bwi+S!8&~$t0IvjHMV5R1%- z-Xyrye7pNCQ&yd)l4e{4k*3`y)K$q}F;7pJ1A&set6UuKhMh_1-IKn}MayrO`I$=7 ztF_j0g+UsxY?iDEeB_$rK z@-_Ttb*KLTYF}SLe;l*jY?gUpb~~WQmhU3A0mBH$+!U5#2Mn3=-x5zWnn}5F65Pdd z(%X4u(nt|V3&ACJa6*BW8&4o$HuR{*E;DlGc2|D;;jSdSRG=LGZ2n6Ks8Btzuuvn)mT-`Nq$z8R5 zZ)<)JQfB#8*-14B-%_!XYl&7XQRT;K*B)Qln{$HUfcXewIQc;XJ4Xhwb=z0JvX1W7 z_3mW4o637p8QT#-wGQwYNCL4LBobXn-IH42X+oW9Qj)rpX+5=W>+)9bdx^H9)%H28 zxt)H|Fm0$2pK`zM!s8j-Glk9ueXD0o@Ybv1EgJIoNpZk;deru28svE$$%_+qwwfX7p(MrXwYak0=gs2hGk$J$QA_l90Eb-oqOYJ_TTKnBLS(jVnQmj;tPXr#o9`zQVdTzAadwJ)APqre7gt0l1$yHU@Nh5Y#=jH(6@t>C-b|ROy zotoLLe;>@!4%?BN$y!_MV$uaMmbmg|MPRD<4Y{&gki_iC&N>WcTRl!VwxLb82_f32 zzqyP1Rz*u#hC)X!_LT9R`CdPWe5auxt2*RmScg)s zj6ib00IJUCBu3m98Bm3SzX{ za;=92OA-cfLj{m$9eJZ267T;20O+ZsZ?Wey%#cX2i-9u4a2RApMtbwkD<(wU%X066 zbKHNET@>PzXDdxDPQ$_LEri#4$wTGAX!h~R7*Mg!%}t89?9<0I40sg&MM+3d zX~P}q0H+wHXPRgSsi{HYlNvl<;wA2(q21i;J{7-57gNZRNU}w;+UX$*%Gm@nGRA`q zhF>j^IP(J3-wk!0Pf*gJZEnr(HLVLee0H+LV!A|lt=v4J+W;M|OOOcw01IR^^GVL# zEN^A(`MRE_*AdNN!%(&{#VVANDfX}+ox4Z`gP%_M$F+Rm-VKT?*}l`_+xcYh@w<~w zu)emIB{uGhxf*F!IM;GVj2+PeaziO(Ez@$>v79EOd!l5%g7@Lf7q_;Tx`mt#_FpGR znPn0}WsF%eM{wCd%)UX4E0-abPo6w3#1;0H)#SG`U5O)( z7?O61H&BvEZ5rfn!pu&^D)|~)mgL^&P2evN+jwWh-XgQrt*nKPr*9eiIt89PRtn86 zw31^a?0DPf18^m|A7fq}u``D%9n z=*hzR0-L?e?+RadpHjK;Bk30UUa#TpXIGL_Gv57<@a4DA8RSLAF-g@~KqjhdZrlbpHT=ZrpvHY{+_29Kio@b>iT)eaJOlA(Q?S3DTexpu z?aL#gsGcYJpDLg(K~kiJP)vCWyaAY}T0cM)neBlsY ztWchNWMiqo!3P9(T0W;Agk^&CS=wK~H0fcry$BXqr!g3$^3GV6+Tn+qgkvfI<*SWe z4m|q4zGq6+G_*Y)`$^Pv--McmyQ)|l*0<9wE$k(_idTu!cTX%L5P1F{{SWUB4*Q@ad%(W`JZI` z57D%53Vb=X)mL!1)SfuzxR5)6x=|9wi?@u(PJPHH#jjGCQyVLD7*r$(Fveme_)UR&mfI`pa zuvZH5MoYJv%Gi@@GZ$QnRHZ73drcDW)m#1#;LRk}K5kwA04;yuen*~PwyznM+h2zJ zP8wWK6sRq~c_J#0m4t=2p;3SV*aGKh1*e=snk$&=&yLZ!T>3ur+3KSQzYpVXfb|LWGSN2!BRCypkkzuvz^r_6;`Dz4V@Ije~-G>ux?ON&TsXO2)LK$}9! z0AV8>e1{@DYzjzI+lIN}{WxEEzRE2!!U-pu>`VK_Mvg6xHek9CF@UR*bB({m19>s_ zlx-Nw{NIhgCi-r5)Jkz_t2>%^iGQbCEEDOrdL^aQGR5|Gg=A}S7*eIAkd{~Z(+p1X zNyp8AGBvF->8)W~ojPQD%O^-p!=%sV31m|kAnsHQK0@OJ$mBBxTJM!PQ;USvo%BmZ z(BZqvHgCPyw9fPb2P*hQd*z>GR&&Ut2)Q z3X>WrRJ>z4f}`d6SOQq7A36sd4cNxIm09~aE2sBb-{pcK z7+unl^5+ZAPFS%7{Mf2`#hN~h@1`~m15FycF3`=Nx=sp{u!GMV067M_aP&Q%VB-y9 z?ADqjlYj8tw<4`i+@#KS{{T;x#Wr3nQc4>w9CEAR_sCO`&t7;Sj=96MJ$my>5xgy$&Koq zoCO2sC66w7`T%K{656Gfmld_iisl&#No30Fmdw2sTnz6q8*%V zW2e;gBQ2$@Iy_|Qb6d(|nn>ohSCec{xqQYY5UEd_01%@)upXqpM7Y;n4A3`WK;DjYJ{qfB!d^8q3- zS&I}Tk+*@lnXt`)%Uk~dqDyZjt1*%71cQ;hq24p-JAMRH%-FI&yUP%GC>w*9o$s@VvxlPinxxUeZS{~;N(cD0i z!0PeHp_P>65Jo}#tDNzVhHZRBrXjetGtF(d#;U4Vf-uT5Ksn=raB;{r>OtC9GM1?E zYi|$fk!m;4#$c0By;XwhRYteFU=B(zm;2qh#|*^f2qjc14CMG1r`_FMY2F&T*7S&E zw`*yhaI(T*EtwuBB*>&PgmC`p_e zjL90Dh@85B>Nf_%2URBkk+@eqb*1WFD6x*iNQ+O@wHsKM&$YCR%et8cLYGsNW?q}3 zm4??)peQ)xic!5j`yF!ktx+6Dh$Pt+rJDV_$+X_F8`!+3Ro>Z5ZX*GKJqOIr4?$f| z!kZtoc!u@O+;+h%Wn`XY;c&V6TeENm2PI2%=DBLSCT&hSqm|I@Z}pE5Od`DyTsV7| zxs}RbqO^qPu6|RGQ;O;QZ6unfiea?BoE=6BJ4k%#t_#N~7SUz5F*rMmu_FPC6O89} zT3*KXx@{cI;kLQ^Rn+$~lF6{mM` zsxZ?1qCLS#StCALuxv5Pl^~wRe~5k8IG|%*s%DCKenE(u~Ngxc2 zkU6KT2AZ+*ey`$tj}*f`sT>+*zP|{TE1f<+v@NcuZ9HWra&mUQz!b>NnT>`S3N3Ux z_rxFgO}r&@V|#CVr`*MP1)w%}$tAS$2K~y(b%xx)naC``8)=hw0Z%k*MroZcn~O;F zxV3G2$C^Xv+O~$*7rG70M|%pv0^NDhs{Nkn8c7m3x4)R*Domw8QIK-nz0bwXf5KiQ zxsOxR{7DYCeC-9jzOc6c0Aij6Bqhnji{;8UWt9%c#^02%bl*!DxU1OD(L8sh=z2cA za**qg?A2zL=JMsCy0Fx)4t&3~GM%wX+d^g_5HX5Fxe5yNp91_rT_aF2TLjkyN0;73E#8*yKG7uTa;$k!0NajwWC4#*pku8;9nIbB zMmvUSql9i(XDBzUWG^GHCp?US-;-QZk2HMEqWNWYvF<)JpGO}9v|G!KV^G(%eNshR z5A9o)l-R_r=15Uj<&Hw86=fxvcQOWL8LuGH;akg%vq8EA1j!nbfsPIrb_oTsjo9_z z=C-S%TJ$C#5%&*+uced1+D4CkYiT}~nsvdvwIeg!$dVLfjuy)mRwOp%Lzj>4oyarq z^sNF<5ZOiI4-)D#=$4Y*d1C3<3s-R*s~p!Q_W*`Ak~ZT9%NzdScRDJiH5qNE>H3p* z88;nFOMephUgdSWZFx0Kk#I1E))2q(Q?Oy#g3~au}C9P7tV~ikzFGZx9?*TIp>+2ZBx0cgW9Up>PGL~Z9g>M=(JDoI=LpC zU9WYguJ8UE?l?OS4C#KJAgA+p2 zjC#F)rTup?^nD^JJ{(V`Ycg9duB|gzydG4N#!xDdG0?DA*z|oP4H~q?=H@XC+4E+}l(*Br9$0=+Bmkg z{Em~yTFs=|uZQK*HGN$px<(Csr^c|XjD=QAl_9d^M<7-#R5I|pg>s%Uj##utX1Scm z?!xZQ@dKMR%~;VU6qpOP48u|ef0fIsXkh7=ly<$8fpaU7W!?Q zByrnYxSsMhY(*1oRo&&LF^$f02yZz3)AJ76>qFBlbmfrT+ug0uRX8zA9mWxYx0VOp z1QJ!0pO@zfT`}k4slThvqE~TO?dN~M(KyCQchKJP7mSlz)(M901E*b?Ab8s>Q_fxU zVHltFjtY&+LWUrL#yIQG2whs;&mD{(Xwxv#yk;qtiCFpJ7?#1$-6RGla88TkRn4aA za@3D3UESWcmde-DPb=@P#=libd%D`iLj*cyl=>3SZt=X{V3OW)uW__xB*TEEa7S?8 zHUP;W>FL7j0`Au7tgS+o5{S@9(z1kb#0=nbfKOJz*@DIWVsxb`%U1sYCuQkv7hT_> zJdu@@o`r7{z;3lkBAi&-Cz9LVG%4f&o@UX&AruX`BOs>0GNe}rYin+7fSOq*E(Xnn zsX1&Ac;ueB&N0Sw)3++bxl-i5sVA?Sf93bw&a_uMWr(gG$&oGmw~b>aIIf#|sW>F5 z>V)H{&*W=P(*Ab3i|smf`YgngEt&_D1O#jWfB;tnDFY;~cp|+GDl1Dy(F@4m(!5)$ z_;S}$me%rnJF7{7TT6v-u*OCfG7rp2`9V16%S@aAD-zpOO-lJk*|TqLWVRCnAu@b{ za8TzUores2Zrjib)ykzfB`14d`uoUnP3VnX9(1@LYuOBPqO!##TT)HKA17VdQSyQQ z%Md!%7%?&lBef4RTI^SfHIPU|YOd{&L+uZOp?7dO1Or^OZmLV<%^J}dy0oH5V7pg# zx|%~963*M%S1eebrw*eY$9G(tlSm38!Y!6ZjEk3yzE=tt%#nxy1{pX5rsIxtT}?iw zcV&2AZmyCegmEYCyd$bMUn0q{O4!3vxGFFIDu(K za8-D~J4xrT2b}E&p-t6sUB9pRCUVl}xO^q}kK}4kp=#1x%p@wXY>c;*RSw`m`GSC; z33oOck&g+~14eZ~mE9gk7Zsjrdzeef^E9su(-KMl(^ zt)bennO-0G__G0*3{-QTnRxpv5)=v==2q&qHl+Dox*i{_UEAHHencKr*>69el$`Yg z9CbWbQ{af?hfeV$S)5%alXE0a*HR!?Sj({idY>(1QJyl`<2kNMs?^=inrhRrbK%Z| z_N_Z#*Njk1bgedfh+~i|jp(3&=zoBMdGCx2o-3E}X`W45>27VJwwBc)wRpo|gA76} z;PQElpU1UjyqtWCo%t2JnL3(0P}{^MXND+7!oz79B)a3-%Z&R~uL)@9SJz>T#XH9n zZ!tuznFoBbs}%rpTO0s+9Y*PL#K(D;r_`5Q*EO_g0gm(&W~g3fLmv@=oEBoEXCwIY zx1g?Z<5=NSF94pO-Z}k6W|fL|dY`1=((d%HXz|6}PTfGq15OQ!mWmg7rw1g`unWWSKidf z*Sr(sdmR@{*8D@_Yi7`IZG4NJQqEbfWOx}+2&E{Ql)v2p*`qADE(@q`f5cuI@aBu+ zT^b8rM^uMO)U2Voy}8uG7qnvSxGE$&fhf6N4(-Z#QcXE<1S8{mF~53tni1#3%?KBTxl1P$EoPoUo`etQWQ|Y zV{~o~V`7A%VbxfJ*pFMe_>JM+OUCd`;VTR4ODSxlivB~XeBehGA@boacgYh0v4C=< zyDV}!OkFy+T&z-orFE|9-xz#Rr}#s_?IK%F^G~g6a+end&N)a_Li~hfDE?4Qs_wrk z;j3Ro_*vt7PXyd}e@eRWosNgB-T7DgtP@W6lg1<q$k)-7*xC zjhXW=#D55CegN@onm)f0$9HcOZyc7+%@nBPAgeG0fOoEO)fjfH53$|-h707gf3vR5 z^P+r#j_s;@0(`e19$RA((Bqqt#oG7&u3Ks9i%_tXtY12YW^7~tanqat0Ox`0xJONoqK=z3nc}@N@8R!> zH4R4I{F^!LWZNvdY8F`h&nf=^cnp4O9F+M0JC4T1d9J;GtXgwODPn8;P%D zzJ+e2o@nKGkJ_f)hjlV42+%$w0#1NNIa8@h5^Go`r+2bCT-2TAq?`W$KkM!`{4+kE zZ{r8kJWFr=p!ZvUvL}LM62#tckzett3hvL zZjj0#wJ^x36!HVMR%rul9I*@{EQ#;s#H~*cIa{myoUebmvgq@BXy0%6AJ&I8EZ6=d z@UEHXv~b^jsqSEc;hV~eMZ%cSyq_YJ4=Ry_9!QM`m|Htjc%Ii;_+zQ*y2gtmYZ_0P zc{SO!Nd>g(cQ2i}N#T=YJ1NS7ulIpD##4=0$!esX{!2@KNV?AOLZn<^3!W5iQGnn<95*T7AzQ5U29)a;R}WS(6rO75hME~ zGPqN3=V6HR5m>R2%u+DM2nUoRMz5HohMo$fAq3oHt!|p~wEc9qllh*7>CN-UOKkrD zubDT2;GV)6^$38UO)tJ{e<;X?-L|SMk-iioM=2$;Gl$OXe4M|Gt~C3(H2e7VIAGP` zQVKw=2$fb@Ld-_M1dS$1+De}X`;&zR$%E@#tWVl(NxXU!NIAW23mmLSof(8_2bBBM4H2G}oE@zU?2<=s1c%xgv zori?Vi5P|fx#XUNNTX(`-`KHkK<3pD)h4TXI%X4&?|+f&(rHKPxuV$xw4{ zZ9Y3_p*nof`L79)CA5(3X&Drt!BO)U7{+$s^PY3(;-?rv-8Q9u+S{jwpd9wy@6SCe9GP*gFENo5}>$@V-P@t>E9_p*q!M$_A0N8SDB zHAfcj9g2{=(%b&z?M7p}a{vxG$-(4xILG6`txvSZ*6AZjHLXTQnNZuHDQNIS*Md`#`2jtIL=APKYt^F zGt)eS(;8Wd_(+ITw*XOv~J2*LSro|y+U^<)z*)MZ^hP*uj=#Bg(ik=H+-4;6}j{nFDx zEsYxnxzsKYwakw4F!O(QNL|WT1mmv&XOog~lU?nfizkD_)}9^GtYVfloHf!5Z#}Mo z0YZ>4zz%Rm81aIjY^~@w7UeD0gvnhbde?X|))=(NrL+x|A_oX*Y3PBRnq? z2USqSjCKbgpJPBD27GtZ~x zJW%T%O|`TEofc^Xi6at61yvM-*ZpYqu4_<=_WNI&%T{#zbhoYcw%CqglW`6=u1Q?( zI0qfZbDF~Inf=Dryi)m?8s+%A)o*VcoZL+Ew?IzjC#HBj*I}nYX`_5Rzt=6U<7?~4 zMU+XlK=Jv4M3Ff82=c*hdE{~m^Ho>5%knXU_qmQoPPFrxrP10&B$rbQkq}7s?nwCqYVnXyT#$2)`>+Q}SJZM=N9ic1V!gI_=v;bG0i;msaLpl# zDZ-G+1vna#hXYOqm4f2YPQh`&8h13TJDK;|LTVa?rOcO?(S4a@Q4PJzMv`yalHq>w z0Z19?xRK5XhWgI_eNxUfxR~iuMLJywoJS_++vT0Z3=|FHDnkNOsN6y+F?G#a$HIOW zn*QOf?KJxc#&>+EiUt)xP){cc00*xC^shVAzAa5V!*6Y(T1P>=1@aS zfp&tpJDV(VfJZYaCAn~emZqJ)wFT5Rejc*%4}I|u;%$p8dcg~>SBNyy*@ z>FM+R$HI%R5&Txww5uznz0ox3YfPYMMreq&BiHUiKuh)T5E+ncdn(kH|2|x#CrmYDn>tiNtIIn)BNq zgIW_or`pbLF1*Rs3!Pg^S9m}z6GA!4nI>DPh;7P>pd4;jQ>5ah>MC!Sf9w7n=C$t! zcyDAfXG*U+(Q=i^tc9txPUjce^CGR+|Q)xwVsb9!Xhd&Xn{3YUeXTO?j zjS}tl>UNPNE^ZJjE(noZINrw`Zp#de=DzCD{ARu-(=9d42U62+v^G;6#e{bLZOciG z#|0%jPQo0-QJ)7G-SXDZdUCUxOR3`j037@esd(SOx85U*O@i8Q4(<_09l|p;qO-AM zf)E26B!B|M1psB3h50%+H&#(VM2{>yKo(vUvL7{}L-#=Sq`SHrLPOSQ|Sn)Youk~8)xWsh_cPmmTiVyplqmy!qtl}eK$ zslrx%>oli(-?8+@o#2a42W!{ycwb%A^!vMphC9pXZK3il)a?Q&)uUu-`SwcEab_OK_S;_sx+xJ(f+K> zNzS5VS3ePN@AXS-#nv>Nole-Gw{^4L_iC~2WJncBX_=&vK;Dc7{A^hba<^V5)_ik! zDAmp0p)3%np4(7~RvS|5wA>*DQAXXY9h55X>R2nc1xnQ=Pp7^8e6-Z)gcMtSQLEwo zBg9&l+HEcGtuL-^rw&`mX(75%Bua`|qf{a{+&Z(lQ?y|s6{X=FLeEL@#-A;sORGV4 z{%kL04AUb{QHgSowbyKKA$`7KMw^`EDwHTjl$_MAoxc9I@_oMIr&3iHD_uXUm_8A{ z*RFK^MrmE{yr~P#Ji3gR3XWPp`!)k0US#Gr8O6%_b0EaY%3To0?==R@cQ1MQ= zt$4>xv(xS(w@o`^-Nj=kmKtf1OtZ)j-d;m|%ZAWhIy?+*mWlzj&?&^O9Jp^6@yC<1E)lb?BY9U7p`Frko>1H^24&01R(l$XwPT2z7>XAG7 zfk+WLOji-gr0rE~h7E4$cMLoCVKD9f`E1j^mb?CMC)3YU3bnbCg{;@**x_#U%k3#y zZXQ;3hip(>+qwm2iap#F3Rt5mqLPQhE=Bh80*kzM) zu=8?Csf^`YCm7C@oeeCl?(OBDRd2PRj55n4uXiLv=HqJZ$;l;# zG63fVv#DMd6wJf#44eg*$5tCKv*O4Fp10dTIq zbb(ZWr=evdIB3RB6+rvnFP(^~N}SaweXUmgb=UE_{{S+h7U3x~qhOYv6J0s1+8D%< zL9tz;c$l1F$R*ow-G(Io8*@z3^z&Pi=dDc0N|dC-hdx7 zFRvOno6YFu{aJdu{l(Qu!M`mJHt_?szO{Ce-UpglCTS)J(J6egk+7<_Ew~a#InM(% zpKi9^XrIB*D=0!JQ07^O3<)vrKO?XQ=D<*QU<~AT#qz5&8(gbm zqr?JMf$fCs-{`6P##ofDSwa%Q6oT!}M&;o4u9H`{x3rK@It+8QWgvb@}z*Z1CY?XfR|m{et#h1@Gm)L!l;j*_K68}%Fr9HB)p3P#Suj027{(ttlW{w&_j zd*hEG4Zo9XZ4iO2g9K!eeEf{_jLg{J9AhInrpK*K55gI2UE~qpO}UmeAQG;ncay^} z%OSwY1P+}J6}6V9O%-LK)HUUXukiE28WL|$wd%~GZHq*?JtlpKlHPg(j9L(69yB zx(xD2dL3;;_E$#IPH3ZuvMn*B)sN_cw z+Au-n$&l>89sos4I$wkIoi57$)nFGe-%TvCtjV{+3ohY<#s~x!mW@&hyX{+RdHSP zEBI!Ahn^RP{{T$zkZbraBvWvjWwiR5rr=Z~ylVSbDCZ7v32vFLQ^6X|#ItIqe*)Zi zlUcUazQL>N(LJzcn%r)WZ=UK%K$hfZVBXBb=KIkYsH-CG-P_#qe~!Ko{?IV%ULNos zsiDKC$05~cm0@Lf*6qnvC*{G(>;VTSu>_83RK3@4>-zZw z66I>zqAN&%)L-q%A_W8z2Ot6o1aZOXk5k5aSE>9pw$^n0GVfmS)~3lUaLBiIcEtqo zLV;RKorTm$sksP3z`KAjS8!}B?yPAPwLZG=_lxaswa3$K^qorF+2LWfxe$paX53nR z#m@ElTMVpomM0;Qp9c7`;Ws*@m%4n{`kuRcJI8a2Mq9HaF`^k(GyzOSV>oayFrXdV zvv`&5Aku#?>-yN5Kf9i*f9w7M#b`0zXkQUDt3MFw#!FjQEwk+c(c}u`f3DsFva)Xq zWCJL?@}&B$?}>ad;p=&AtTff}HoJ3YvsKqE*)6TnaVm%NRX|Bd1ychha05ABc`)uY zAvHdl+jqA_U1a4X;ne3fOHU5o_z86_62ivY#d16kDh6S1VIoL=SrwnlfqcT-bHgf+ zmv97m^|Y{?`(^es zYvj(S2GpFkTY7eO@;9RJ{{Z|W{!0t#cs{uJ#M zKx%1eeP?|<7VzlS_bc|O;Iw$)BO&A5H**mxgK1-u0nf}U%gkKoSyH~ua``)3TVF=) zZrT?MD5);S&xJK#3F*2^!Eb)LmAuZ^66x2!yhs8z)RX2^iI}R1Iw=5b+xy1bCyIPP zCGh5^?;e{NHca||*lc5Ck^})9t1yX}VNTWsRaFM^6c1vR2w~~Y4|ebOy)E7Ce?3k` zDM_Zf9G8OUj`mGP=JBnsuGP1Fw^G}TAOS}9ZZ4XrN`UN6a(3h}z++ZyE-f@M4Z3PF zwvzkj^Tb=Fk~UEWHWvW4TX5>Y9RBYP2a8&rOibFE*{fRJEw9Ple2$61M^*iHCpVV! z-rvgCFFZ+CrhScq9YaJj~Xdsnt@`-s{m??LU_GTIusDn<-!Dw)&2x6dFalUTN3)jvK3@ z@<{+!k=+9nBoZ46&7G>LxNyulU0UM$*x9Q~H1gb`ON*%5NY(+mFs^qd_=yZtKPd`W zjQrWG4J1Elpt?8+rBAlUh9aX=t*5?tdxAum=dv&Q31>EyIvcz&q zJF#u6++!eNj(Uvonq(7R!KVmfx{}huZQ@3e{!@|4=WgMhfk^MP4wd$Cljd=?t@s{w zv@-Q8LXp8F$PkFm1b-<-1LerT9+@MlC%rM_-?8GlR#_QXyC6WU?lXl08*q9KdEj7F zZ)=on-b!P&zqOJGVq~@i=2M-UI0HR1lhmH720O*0>Xv82@@8m68*ee>bzAk2N@GAk`Qu&B zun^12=K}zC7)f60PW=r$kzVa=B)vL%3%ko`-)E5&axa^ljH(shq#};Q=b+-YP0}Tb z-6Kal`CEvb5rp|c<%s*o03Y{oasaHQOFqW#!`fkfx z((beg^gj;0%xP{m+Z!|~xfCN1vScVEH&Ry{{fVwy#9k%W@AcKQ(6tzCo=BM>3oC_m z4~&w3h=LAr$COU(rGfUFN*?LSX5Oc=SYT&T;$`;?yeJ^8AdmaheaEM9fF zxoi;w$`#lg<7nt>LWW>r;Bcr5Joaa-gf|Yqr6>1#+Hw$JKfu9Y*pGgkvBmZ-Ny-*W4!#><$%Z-#LnEw zzJrF5GxV)yeJfc;)=bwnH}@WG^m1iboxz!ri{QQq1Y~D8I5;Bxl$r$Aej>NkrJl=9 zTQ$7X8N7+6QM&;!9EkE#-IX!c8rIp=PR1yHm zBzGWs^Tug{)!6bsh<3VHjXoCH>pmVfas9qnkzv1oEZi(YRa7KWo z)m!_SZ0w|(-p+g3R~v2MWLADa;JYgE*OSm3WbsyDQcX7HHA`8o?_S!%G?UG;Sk(E> z#Y(Ybk}>n=A9%1N@q>fny?0FT-kixaG2OqMvy{HO+Zq*M`}fFC+zY>I1}lX)83w66 zZc^6BjL+g}(&JIpuaio$DyGX$u}F;U%*(MQno`mEjB_5;8$n}-Y^fZVhde2x>l%iy z;w>*rvA&kmQ&9w@?waZ)SmzTYts@nYrE=)Rg_1E6u-H-5YeB|bx}&J^9;c*uW-(@o z1ee$0KF4O0wXCu`Im8;Rh4t8Ju7vO9v&Ql`!Ihbq7S87qC%l`lt zHQRp?d{fo68#}eTx0I{LIgBGn2p=KZag@nS_t~D}vT}2?=-1+J7^MdK-rpk)9V64( zEmfo?hUVMA`?$xaBfsfh@$kpR*FF^RKB03o_tHgo8r$1j+s1})?<<)6wUBLO%gRm} zh$Zrjql{%yQ6E+bf+hCk}T`GRdEG0z$Dt5=k2 z@;m7*Uder>+rQ1|cE+4)LAa~w`Dy)j+~sezW*UX{yccFG3#k+fa?(dD5R8X{N-pu$O^dj4N= z3UX>OlhHr8x1mimKWMSIxi*kXcjT*$&gkqvbgku~ZVv3>azT6pz^f2RJ&u<)od}6U zr_8vwk|38c$TxXoE}@3qnPC~o56(tFN_>^wM!nXz_1){+Lb+cfihF*KLiL@8_Kt}y zsFTRME`*8X1t3f&>0G9ED>SgEz-|kJf)=~`Uk%#bcpmcq07TVNSR*ohxo_n$O>Hdk zr_%$aayfc>Ugczk0pSem1kSKX$*3~ z_Hd>(EPh5%0Ngkk<2-}cj^@i+U0%;rg6856wLDV9E<%+v#_&jFE7si;4+*3(EZ z&u&<*-`$nk>yfco;ipf-L=0e>I9Mon1_L5*x7M_HmiJ%oE@V8HsOp4 z(h+>pzxAOrl5t4utTkCJ9zV9}^J*Hdz-`rJl6Y1yoFjbSGJyFYml!Gt&rXc!vgx;Q zJ;tbq&9-QD8>?U?NFiJ<);Ll}ltDn{m50nc%zVB+Hh#^nUbpe73h} zt^Un5s6a*l95HjX3mg!Pz&3tb=b9VaaBg*1DznK9N#xA&y2cYLw*__q8!#=5ae#R_ z#-yJ2i`VUDOB-3>EiH>ge$5@ruJ~64j_sfg z+la#q=YnufD!ujP)xD^i&r}l0cMy4^w;OzvPmvlxzbJFHPf|f{pp#!^fUdC=m1O?_ z%S}J7wmj*sS7V!=6C?eaNu!h|V~vrEAyUNf4hn)vJu}ZvYFI6nTWfd?#n!=aB8^Kb zh?s^kg4<3`aNKdjcj=>Vn#gGoTgMZ^=6ji9i2RK#Io-5|;fCRyuW!SlCZo~>nuUg* zF6SR&jo#Q2Z25W+ zNZpV}-TY5M>3$Mx4G2SXrq6WIMQ;B9ZIq;8Bbp#ci6txqfsCcHoZv1wA%@U=tx1W+ zG;sG3+Ql}lHIK~q${0x^#Nr@XTm={<7y*svY3cwNt81=Kfa&qu!EoYSiI#IBuwBHE zFh@DgMnJ*rO>)LlnsJw3^8WxoQ%hS^I_@jmTlCa!Eu(wM&chsYut=K$j?uG^m5$Sr zLHW9hbka?Ga?N>qCned)j57g%xMKO-NnCJAz$dw)y-Qg~R==%971VXhi)NVH2&3|3 z@?5D>Z~y@8>`w#h+!4*FC3sxI?!j2%Rb#hi2XWlzJaNx@9mk5byZ50*_(}|R`_}1iI0x-wzrPnSeDu~ zniwZjDJ60;PTX?|Vylo9@yik#hsJGq-`Ja|VYoVCMC!A78&*f0%y)o3OgSoZ_r6vK z<^(Q|e9_pOS)L7}*_+)))k~>KXH{(Sa9af9*dFz*;&Es^U2kb`F_OyG(%BkUO!8q< zCj{VOnT7$!9r~PJtsPPLPecB}wbMQ$_-Dh{HxWr`4a6!PB`)YdXKZU}pOKKeaH6sF`?gO6>d9)3Z)CZMOXa9+!}Au% z>y6?rM|^%cO786g(ObyU({-I1*3#GQ(a&iBot$3?(795iG-w_plZR}QO7gkE0E|0( z`+Xb4BUJF*b4_!mTnVh4By7KB8`+HN6mOH}KwGd32p}%q)UJ$TntC66gHKUjn>^G= zF-!7+(vd=Nc%x}OD7aWv1XO?zYD{QxN^p#mQ*qdMh>BcuM&o)86usyWpj0<@R`>R; zZ+&eI&AX6VDUdpx5Jo}cf&u!9cLbT@{{S2O3w3>`S!x~{wTFAlBscJ^Y)rAb$k-S- z`J`r2p>ppsl12`FTf$b_&8LSg?Yu=bz0KqwyIZJPvvC;vk~*-;xgU6ChI!m`##Vgy z=xucAtu;Co6s(H-OQ+^uly->rN^PpFInZ0Cezw4H!>t@$b(^5 z4t&&M^JglAV1u68D;Q^&S=DA9Zog+@Y)!K&nC(u|_=qu4woS-MEH?#E#dhITY9}WJ z(#DRPQBpjI;!ldM^-qPGb*;VBFKMXSNppJxB$p2UUBqMMXxWTATo9qj2ky2U5nT_% z9X+*=0(gT%@omwP9bvrq??1Fek8lKWqxo(a?^yQ2rGY9-6g@C==aZ_M?Wt}mFn#WF zT6Ujh;-3d;z9I3 z({*>V))F%ftFi^!(LuGX(11$;jjmCN`J{YhSXNY}&GB{ozoqz_*U~z4{Qm%__!~ML zqr&z!npUH2rRiE_ovdgl7X~zvVhLdwU_o9S<)3~*%8)}JDE*;rJU8P100|||nRsu! zL#72bacQxApxi917s|@1$vDVRcpwrn&2)P!$Cf)Bv7M!J=05;@LBH^4immlKGw1Aa z9nGA$+bn)ya=W=J^D3|-q4gODBgMWYo5lV;*0tS58X0vN%!?R*Fv7pUp^h>YLauN+ zeih8E81kcMcJ9kML{d+6ZxyAqcMOF|QtOErW98j~2sjw@B=zfF$9o;EoDf*(`btRg z7_KkvsF3!C4>u|BJ4}D8Pup?fR!6QO{@x(Z~+($#r|CQyQo9qzlpM2cz*KgSfdg&o7K^v zm8Y1xE^+eUgP-Chn3K4Z(TReuLATW-8Z{j{sYj>vbBNUewzRj@Bn>UH#LUqwu^47z z2ML19ykuo~IL3Nroupg{Ea615pHeVKvgX=$P?+7hVvJ;*67QY6PFx%UGhPdXeVn4- zD_VMa{wn8O+l@=2q?RzsA7#8v42<%Y0NgAn9n>QNRTX@}oPof7!#E0c1X6vWp5DnX zuL8H71?B2Y+sO{ZFl;{Q2yO`{<^v=%E|=$yk6V9E&u>2Zb}3pmYj5j9*M=n1-^4Iz zdX3z0&l-6U+(06kWZwH<%wz*{e9R8kEwso0P{z7lUitNV?PqjwK9O&9k;V3FH1lT3 zby${2)kgN(80~f+ybMEU6}ojNPnHkYPMf=5(f$TgvU;4=oxy8K*3@0Ky}@5D%|bW@ z!3vv*%bfXr03?zI-T~+DFE5u*2rVUvWx9|(!nQ@oI8vLss31N_KQYHrpp0lyr%J1m z()PdI`q>(|%aZX$yAgL4{-QKBNT;?|m7tBoAdmfr%PVrtgU4OLhagF)$!%+;>Xur) z!Lw^98N^PWYs+w2DAi+fHzv@8wn;xL1;Arn6K~qJ6?qXAXk4YG+SplXx^2d#Z*cE& ze8?t-MF9?Z4!u_xMg#+%Ks`#>Q44sA_H8Z+64uIPiV32*Sr*wrY`VVHD!aEhWxyFZ z&UaIBmo%m1$dJG(P8Qq*q8h-1`Y(j-e6 zY|$l+QDcwIRm+g16#xZy0}8;9dVHsq*L546BgA*!9JrR&3l)DQCTUsL44B=Ud#g85 zxcU-sGI{8y3RtX6t5eqTzS}sowD~r?(lVUw735dC)%>kZZLUyUExhd9;={(#4mpW@L?*~k2Z?3x?>yLT;(<417$j^M<= z%*9s#YzT4kMOPbeK=`G*hllQA9m?5}OE&-GkR&dO_L*U?&K2eUFs-5N4-QCOP>fYW7qj{VC zC0HP44p1tS)W0i?f(XaTz;jcIX-cNHyY;>MuPZLY>!yROqMlu>(py4XX4Yvw@_>GA z!?tn{VeQ@bH*!Fc`7&?JC(3k1Ckxt!d?q+H!d-?~y+ z5FEz64hD9wJq2_-sb8`+f=(;ceZPpKwDJ9(r>H=eV@|d#g`?bMljd++1PqB=83zC; zBR%-3n(JECo_3Y4E@g8o$i$Lao0enlPhWbfYn51=K9af|gMm0l?iO$Qv9GgOi>(?Hf^l59)wk7JEyT9(-xRE0A&1gPah3aZv@Mw#}70 zU3waNUxW1T6I?*nR}-b($PTvCBEph3=G?0*z5v=61m`4?)p^tSi}2p>z;^bFG#4eM zw^q57!HB~!e|c5#f$Gptrtg0YylswsA>~ z5@YeCwk1aA~GF-w_LmA4}@Fa<*pqmlu21%^P#$)QDaHoBThO~o$9h#p$s0KzR2%fqi@ zWp#6Lrrs~l75WEw+hGDohSV_34&1gg@&G_1A~_?@c2;MUP6f z(vGugV`%Ozt(wi`RgyPhx;a#r%4K&pH*Lurn(TCc3`M6{!D(T8dmfz}Zya#5fXJ-d zkyU^U1Y_k;bA{ma$s8koWQ6KAtZ5`V#jV5bo(s!hR4UvJlAOL>i>TZ3sluktc8tHw zNaj3ktZCOC3cX!Y-e@i@3^$_6@?WwUB!?G5G?$ce1VpyRU^b9Q86wr2A!(`ckHqf@ zrN4=_PY`I)O4>E_lV5qV*)^nV8%{#1BMr;|csan&oQD|skD6~X+7?$ua!Rs+pO+nT z(Dlc!TIH4boz9+#ofeN8{{U%Qv`srCE+j-$A*76Bk77VS(>zzzehFP0SMc_!c#-{= zRl4I9)3pDb2KV3#g)5WZI1)s*(|dV5lLB0NnArXll?f zA1-3>%xs~7w#5Gcy(N6@;2tseNhfaJjp{-0F*RfGPvrhz(>*xTakaL4mHyn96GSA8 zt1&Uj6a1=-N#2D30Y*0rbBrF{Vz-CSm8A-@NLWHh$@{O$fY=>D85lmCb-^V~rnP&2 zPt4I%=G*$;`hO!%(@wOXP`F5h(%eY^Si=a|q@4qKuE-bW8%h2YVasO}{X0*dOWlcS z46_a4V2=07@}^~CkCXfgsvjWZfJ1{jFNBkp05j4p6a@0fxH z(~D5G_uu+j-o(9EpzjU6o%OD<{gz!KzSyg26t2!snhL@lP-VVSG6p_xUYQz&qRnk@ zV7Cw?6JIEjJ^NdL*@DnLeJsOe1=Zz{oh6#q?gcKYR|71=AZ`o^8-XNba2C3FXOqL0 z&}p{*bXuC<85;ITqj=0au_KM;OLPRTatn3H#eCi-P?X&y)!zRA=ckqa=FQ`HzScKk zwv)t9BU-PYHL!VQnkNC~V8ON~$IGr%V1l?Sf}|SClU0foyOL{$O=Pc?75tWB7~25> zW)6h01~lXo_lO{YljT7vbFbz3X>Z4()Y5X7GhJDoVXe)s=&d}ybC~Xh&E3OHVpye& zNYN=Fvf;)73KxXigScZh^TL+e#rCM$Y*F3nmT}0gWnhp=By7aKPf%7ep?2Vw$s-s& z6e~Qwti7bRZ|;1nvcE_8Uv^;&O{o6>S0&f<>uc{ieM-S+Hu9vGS5{IXxiEnFD(5?a z{p<6NGIIC=yl+*2$8ye(BU>XftH^>kY;Xx&oE`@oh#AIf+{tRxZNhIw)3=cEt7g`( z@$)OC>!K)u35*d@` z>FEO`Y` zu>%X8$G^-+Ado4+NrYRH?x=(1k9GzzoQ#3fo-^Clw3AnA;jZUPr`W@&7n)r~qPIC> z_X@9s#z6s?Zfuucz#L?#ZNkLbT(W7F?`s>+a1+(rve;lG9JSp>7SfU}~OB z9z=0VB#AP4#zZ@nvz`Yy{^;xL^bxI)dsw7ZLLIo>-#;%;f72E3VWmH0oUNi=#|)O`g1n$caG4=N9S<9hdG`QzHFn6!3=u}roXD)N8WIV|T=0LoN$tmM3emOq zf-B$NnJ%6i875t_lNn?S!0EUaApjZYJY%WrS$-yrTH9LMMdk+^EIi~@hoQgvn7ac_#k6}OyxSpbn&;o`cqqw<>+S*y}WJq9zOUWdR6;VLR zB%Xi{^sF-m-^RKX%-^}ywD}<@T+Ek|;yi5tI4la1Tc$g5K_FH~i1i3{q-`%zk6Ey` zX-rYJ!ja!FlVCzrG1yN~`$H)$hFq0u&0nDG?`Dn78%Vs-bRX>4FCm&Z9nG6gk~>7D zvlwP+3qH_DJdMKx9noEEeU8q>mQo91UHeF2Hf-89 z<)o2@E*!N~*Ihra>#4e$bh%GL(fo6#YTA|UjA=7m*<7`}zh{Z&RADPdyRf-Tg$zpo zyqq&^VlpheZY+Fde{A;3d^aFzn_Z4#Euxfc$}w}a1`#nA$0!l|v+m!wjSzpn^dZtckhvSB(56uXu~#Ri3k>-r7%L zsOWI&I>ol4N;AljM=H$`jepjitQfKRgJ7Hm+wz`{8SQ6_kh%GY&m51Kl9^lshT3=I1e^hZk}K>@S5DJc!E>&w z=G;t{kwj&M)B8d|<&qRvEC-lb*%gZr%U-m;68Zn$(vhbn0-ic*9t? z_?0b&r8b_I_I9EfC5B(N$6;?SU3Q?@42|W-hAO-}JN%0p*UtX{5%sMu^Wr|SCWK>K za{FE5m6>j&{{WVfWtp+L!P~cjxHtrmcf_^HXSvS_a?3-F((Z2MxpM@fB3lL9?y$%> zDU7h=JXb{n>g}z-ns)Xu+kK|;7AmnS{nAK(mpGGP&n#Rh$!>g_iahCk{{Yvi-AkG@ zul;U~+iwuVZx#K``sHp_ppxC$*l#FEbGgYQbYnYDMkAGCr#8>UH&HU%z_Pq@GL?>C zj>PQ4C|!kl%LT|gat~f>jnZ}LxXIaVr=hyU`zD`X>-8to?jV<4u{QQ%JA0=##hPv2 zQ?S8i5;qI;oMF|EwndM_==+jeE0>OKvK4s&+qf|pVhK=v4>{wC+n>U*TiPMju5UD( zYx`+tSY=jaa0XE%Ck8T3(VdPya(LQB6U8XEXRhBp{{XL1Mw6A0-?9GyU_XfEg=B_B z)Z|NPV~t&*D>?GmK60mW;1Iob^0}&Z+I8shcB`sMG)WGhY-VPPZLt{|BP2_eE6(vA zIO?GF9Q4zaT-?`1{{R5SY1uC`nb6WbTKh`1wSr`cW%944i9S}1ommSLkT6i5aJW!? zYp=RGmx(m{8@*}eIbwy|u6f)oex=7VoI^YA1#ABRs(2kOB&NsKsKkM!; zjGtYpMiud`iFEO~TU1fLre_z(8FQ-LmrQc0!GfQtRz)26;r-|WsJ5q{$3w7(&)b!l%j=+P5Vvxt{yO2)_{1(`@~!Bim}7Hl4IU0Azf zqh}kp*YIC8be)NDiuadK_8032gdfJvw5w;W3^lFU-Jg0fq)a>yd~q=WE?bE+SZA z6Gj*$N>yXVSCQK|&N=J#tzjE0%dhqNeuQ57BcQdnmg!8Iw9OEL03x@Hs50U`$e=R< z0R!(G{o#z}t!o;crKX#6r|F{N;@#o`H=5;#lrW7Z6QLerwlj_x@K~u|J@eJ9sl|J% zTeZ~PS}|_=nYt#W4X&LpoeWDfs--6K$uNLOVib@Vk-$BU2qP8K&*BX}>QG|REv@2p z3}cQ(MqDc|Q64!hmQaH|vB@hHhN&v*tYrTHPrJ+S*4J7DXB8HjD47+$BgN7jT zVqB$7(ypx9`}MN7;qzdYw?%u|_1kYQ=1oFuO9@q#Lop0?kKKo0d1#a79WbO07PGY( zX1lt!I;6p)K#^TsTQbS>ZpazQ+kuWp1%n=PNpvBDZZ&DeDQcJH(@p;X7hyEkjHP#_ z$plCtomyyukWi8wF~QnOjzP+io(_Eo7PdO+(%rIo&djIIZR5%$24m9&j^!PFcsT~W z1yxjvaYD6=J1*3=>q)Jqyd@*@(V`Ok!9osja8!0A^OKQJ)1`$K{Pbro6=Q+jxl+fR zsRubA00HiJ$1jyTSgp4)C%Uw`jwMW~3hd;@xL3)-?%kGL1>}*8o)7U>)g+lEjV2Aq z^8%0p?r;ep4hI`X2c|n$Od`~k{Qm&2$jbLSKZlkntN4G!;%kM5+S^W)Nf{{3GZkh` zDN+awqaz^aX&ixyy2@I3bHQTP3z;Rmyt`2?w$|HhV_dXko~@Y>k;oke4(h4=R$gDJ zveFlRXAucM*`%<*IVwRJ;~l@Pbl(ni_POz$icRLkCA(OJs~+u=vwWL}K)LF2xEz8` z0o}a}QoBSRH`cCo&lca@No^DpPjMuV6ivTtJ7uuM0&qv5`c|)oY#z?k`S6wV3gOQO zU?(}q#tGYl{=pT`@Y1nwSvD@Ue{*DlG?xSrnIkyC$>9G0z<48{z}GJo#lMj^n`~|_ zT(K_YxX)leKDnu+z1yP-k6Rz00lh^yF!jZI=bneFbJnC&mFr234j88f=dB@+2b|QQ zjUkR+l=UM#ibEbS4+DysWFs63Ob3Yp{u56M7<8%FVl-XFMpX7wO)zLfk=D4ciMqaz zr}z(0wAba8Q&WOVc;mKZ^O%)6WZcR!qvqs}Km#PGEK28^_`>QvDbmy7c9|2&t!c}t zT*C&Q3Y&A~#}vq_Rn?GzgaEJa9hh)yZ(I1F$>6UHKAI<=SkxpjPib(aWN@(JLgpl5 zhxf3`HmD_8NEpbje`uoGOG{t=0lR~gQ+tv8IPi9(;U9_`j0pwp{)S*vYpJw>B2;zU zHi0G|ExkzD!BlPoSGaBir8;qxQ%qGkbmiGi?t4VF32>p>F&RgaPy+9F1n1>d zU%Q+r=Ku=wC?V3Xelz&Jd|K8&XpYpMKHZ)#Cd{bCX%Z{$LGrLWm;#DipDR`z zQzhWrx+>~6LL?Wr5B^_e5>Kl65Hrr0oLuVuB9Jr z(dD(ahB->aj^ze^UWeohj1t@fh09lx+7S(`g56o z+0)3>wCYP?JFVk9YZ|x={{S{I*CQnM#~$Lnhe7dfqPI*gwJWIft2pIUhRW@ydsPXL z@CGLhmGZZ6HcILVE?j2nI!~Dz+nHKMPm8tN%k2u+L!VVI4D!oyb28fqM2+SjE+*PJ z{{Wu2B#qMG9IDry>K+%G#k|$Jw8M`v5nX}g?Fyibu>|9S-48qyS=7a;$zQ5g6T>wZ zBt9(yNbD`=y^;}P_Oj0$a0BN^%Y;A!3=m{tk_iASk;>rdY7r&IpEE=nE2zsco>ucD zkSHgX3ZaJFf=Yw;PXjf_T1%cXT{rapt@j&Jha$6j{{Uat%uf&aW%c_@<+yvxdx(U4 z5?F~>BLQRT4)UaJ7{{qmf_3b42yN}n)%A_^uL?A!65o0i9El!4;00o(1_K4>jO2_e zSBz>nbzhJCF|V>-&NIF5_!4-W&2w{fmX_9*Yb2%25kL}1;|{8juvHi<=5E?fc*()R zIr>AQYO{-0it;i{C5Z)O9S+dlK{@Ifcq<`u_kkUfS{$(zRQQ*`zkwmbo%Dgt2*^Y%)Ba zdm=Fot`S25m~GLfL9S!N=-PjQ_5Dfh)*-2|NP3`SwgqU^4ej!_#CBjv zT%#x?D-(cNt$h7Ts&yjbuYEr6KfN1DJdNL}Z$-Wjs!gao!b^x%5iE~BRAF#YQ1w%^ zDBQt=0(uM*`T&qXZ@){4jls9Hy1TzwB4`Hx06JS^<+BMH6RsG>{{VG)L*1|=5Zy}N z)hj1&mAb#fuUiX|(@{z9xl3QvwEK7_(sdh+C`e*gv?+L^)m?m`U<$5GYFw{SskK{i zATh5vxz;R;$}Y61fRTl>!xo^T-bk?=h82+$Y1<%H-elgP@cYlqJsbuX5}c|@wWghK z>*k)z=cyGdC#P@tB6xdP(?J`8D`CVrJjejrO9#o?6yXUNPQ`#RRj2rMsIF7SmC-ZtC2E*MDc z6tgbi0SkeL9SAj*Nmu5KX{6)U{{WBSwwv}Ewyo|~j>hKd>?Ne~I~ENyi9*B!5D*-2 zFj;{mvGZ|&HyhIFHiKHdhFgII5lE}KrC9-1AOcuz7y*Z1I{n;WQ>zrO6@5R;{Kd=M z=ybbHD(2p4uC8K8WHFYSD=ftca#@ph7hzW6K;goXkSi+c;ouV5t)Wde8RZe(y^xM+ znLme|;0z7G6OF?l-~;ADwQsD-FG#D}@2cr;&1>cJHL_~u+o__rnRNSib0xH`=RqW6 zc`OR31h39QoMD2MB!CYkdc71_&3R|0>YAL8Oz=w6!uW9;jz}Yn3?dwE97g>&s43ivaPxuALmiA?>QsUQ4y1Tf#Rr9>2xkx9rKqheNu&Empd2uUmQWem| z#kvJG%a`FRMt<2(M7G_2zn=H# zwQ`hRhCJ5SDW;^er1M%Um|#|rZx~#G(BXb%^v-zbNVO~@PZt*RNh$Ir0U($KU^})q zVV>if za7o8Okf0x61#M4VNa}iT!@Fp89V^5dL*6~B-RbjQJSf3L(ZLJ;?DL4#eMcu6jDmrM`P zPy@Wfo=rGOj=40fGB<6uUZE|VM(asQ63M{8fI5Y3tI!gv262!w1_3#%Avrc`?H!FK!aLKsHV#EG z-(s^I;YN5FKbLx7QN0(J5yEU7MY2V6=I|w4jH$;XAf9+2fsjT)^6!e8<(GqeC#+t@ zr(MOY_@S1{=4c7Zs-aEdOn{^&?VRr1+1hrv38lN}QhHmt^7=is`4T$YKq3phK>O+r z7=TCyL6N|~A#zT46W)0D#=5VFblo1$S(<6$wqWpGHMf$tM`DI!vPOxWNEs?PGPd1| z_q@7Qw3}U0{{SPhRMWNoM**xKFT=X9NvBJXv>op9?MbsRU@^fs7;b;O4{kVXc{UYb zL6JxZK{-*9PfzPc8a%OntaTf0U!ogq*Ec$q#KU=U%Rf+e4hBztxUS;f&>KM$ENL4n zEKMQWGx?0J(U7D7 zc#QQc$x=5CDmiqSEp&TJ>-p{ETgZwmk_T@yYm!a~$T=)_j5Y>H0fv0{h_ia_ZM(ba z`5meXP*(H&EJbl)aPr+jV-b>K&V`cQgpTUmVlmIJ%1=>@=K#exxV+Yq4-MMg4eE-K z$uF49`6rAFkT}OuFi9lyS*nn4SL^vF_zBZ-(%SU-8urm@dUds++xQms)` zliTsX{4q5pDs=3+iQv?iOP5f(o$MVBpDakXHwuLLu(%BvJeJx?1B{$61%+9SE$!M{ zrm(oUmIqR|Q%Dm6Mcfsma91ZTKrNGljDQVwRI`(n?7u@8MX0R_bqn1a`#oo#Yl&ps z1Xjrcs~Ge9#F9;;a51~CK?fm!gaR>qajwmAeIAJ!c|OT7Q6k3C$05PXA`*T_F3vH5 z^O6TSJveGAaf@$ff9vKGsiQi}%{o0dz`8)cK$r8yDi1N&dU>F1QOL&OkloZA6Uhx{ zUY|_Tz8_lIUD;0-r6roIcM)zy3o#26t$;y0VVO@Gun6i&S0xtYrK)W&>+c%;km$5c zL9~57#jZqG$>ls&#zh7G@{gUHqEuJ<%hi2nZ z)wHXqB=Th3(8`6RO*%y+;QaYz@{r^eX2OlUhXZ<9jPUcR&vctl?$y7VUb|bkIyCD= zbbEdGIekyUGg|0&moREp=1)3UrLnkRvLd8rQs_$+Da4~A9IpkkNWpm~O+`sQT;vBl z9P+sb7(KbpJJ-=*@leD;wYog&w45bt+>PGiJws)wX*tRxfPPSOha8V`Gl9)%Uf;a1 zi>*Q^*5(vgVT?{O9^IOxiuqh()!!C8TT}t}kIR%q5p|;n0Rbv56dfxXJl&6c0byur~9?bBq+ym@S4_;X9Tzm{u_DYwaBg5}Q8akFngd+#80?~}ka);!W%+S};U z8(3tyyII|~JSNn**x;_kH~^5!0nWj+VDso=XBpLWqrY2ve_wx@30gNMgTt44ozxNO z^T8ZM0ZV?yAb$V_9-jB!)xB-oVBW zx2f3McAcw&uI7;WvGoqSlG7W>ibNR-rh-xTu1{tv1J>Tep8-Qk)5QK#d&v% zwSVm`TJfZp?QyiMvE1w^SxMl4c>~z=&ra1ZS(?jAX&DkQNRlzQfV_j>jCA~K(X;@X zN5StDYXUe;@Km#ccDe+{vJfPUN1h2n{{S~XDIgEOTg@h{L>{^v$A;1?be`hk7G(+q zcwRS?oD-3clsD*cT6UU*^6If9O0PY~?qN|Q0l1I}3|Ik?kar!pbBu6TlMxjr)98k- zk~M8LN!fJ^n^cNfbD820WJSCXNZ3k+1mQ?M)bYkR-xA!l?AB&Ll21PAosR+3VN|!N z%DF6gB$7$aYdkWGl9KKHeEXF<+Q;8Rr$I}_eI3skBA%&u))p^tzF*2P1dp^9JSfj@ zaa`%~t+K*!$ZpEzq4R!H#2hL+DuQ?!#&MkVZ;m$Oz^;f&O+=|8p%E<|I4u>i|aey~58R?93!OAsT+}2d0o`!+e ztxUxtxrK_7yFtk(eR%6y$a13~XT5D1seCNPP;uQ4z@R6gHCH0T)kyDBV0xM^ z0hS#bicrs=y+9HP7WmVdnyEUfQJUA?ne(iLr5;NRkgK;j z$=mOobAa2SlU0$dAheDp1tc<~s}kcQ1y`{7c8mee(d;YCq>_&3Wo;#~k*MBH{gZbD z$Ul1M$7^F5!OlK|^Q?qhVQCZ)hcbhaBR|T-sL#uc{p{fJ$6|78MR=_VjhocDAcErF z-K?d5wX=D0!Vy4VHxR*z_FN1O{{W8slS>EKT~lHs2$*gv-9sKdK?kYp#sC$?h<9l- zcU7JyJA3;(YdiU4 zV->r;$x5Iqt(9MqcV`5ENYBl|t{Rlnw>GV7Z#83ASCu&Q{=dw|l{IU7nJ(alSZ(8S zjdL0@M-!ZF0I2{m#~3|1864ElY@!e?oPC=41kp6J0Eifoo_QfeU>3o~%sJ0eX*Q)# zYgztl_4~=CId1nYUEJL3Qna?SE!!$2?&a756&G_mu^7Q6NWf9Ef^$*DWp8t*TxvRm z23vHDgtLxPWOWB&k$K@mqJy09PCn@(BBM!KJwLDWHTIs;7n;zsu4&TgW#frtS-j?p zU`7b5w`ks{9Q^B%&jbV3tGjA0 zY_y#c`fEG)vXUb$%g$oB>KulNq;wk@!k`)NfO?g0p2lIS)TrG%FPhiB{{ZE=)6BUn zjh!0)J$qNM)giRGi|nxBVVugwNdvJAtjn~q46-ru$PC9nGj68Z&daar*7IG=Y|Bj1 zTi(WQB2gYlgbmJREUKX4mm5@qa4XBGqZ!Tv40ad7^GdL{vzEXr*w1S&ToS?j(yx->sCDH>47-L;&51sxBu@c16VDt@ zBvsmRzmv`h=vmkv_+#G{N{ez%Ul)GFN>6G0?mqv4uxCcyt z${2v$^uQZgYF#S!?mOt9l6mbe9Hq-lcSg}j7cj~S0@zjB;DMe_0M1R)!>(7))6dcW z0FwE32PWER>HI0BTlj|dN7imoTQ=ftE!`rLU9rgS?YYz_RZ_)(VaOb2vo(pd-7@y( z)LOmU!y78I5e|QH`xr7~3x-n2K`zmpk-UNCx|Kb=u+;s<6{FYh*L`i@MAuz3*4)pj zfo%={0EiMOW0THHNDIp(!Aq_N(3|iX)G7k1GqeW4>P_7tA+Wq@bvqGxW{y>&5}XzL ztWbu*VUk8c90Sg4)UMluf_C%l?qr>v&7Cbyo#I(_jRG@oWo8ZBQA`flKmg^5Cm3cx z8HwCVgS&PO3$uA^aeQxVy2u~pAYKJTGmOD>gq#WkBlwj&bO|NL9X8Wz5 zmap_G&8;>yt*&nW0JG$d(mS1&2hF$^&=zP|70a>{g~mBJ9IswU7`mplJeGGtcS0m( zNg$D!7LXxTa;$k}8Wq$F&^SIwSzYTO~sFrsNknK z=Oby)u|D((Ba&!U88EH2GH7FI?el;ZJdN1^4!8vV2d||% z8wq7u6%=6;%-ni1bG(jomB={s$?eGGlwP(AT+&I6)xFfvTSXcwI~5T#1PnIt2Ot9D z8TPF7G9;47=jJ3aU_oD*aycF7r5igk@3HM430@071^htOZNrP}Z9KGCWm4*?CjO%& zW3cP$2^jI4ls7U;-dvGew8WynRUnLyd~O}P(3AGBvDye=0VMEE zdLNCvGQJP^ac|}7o^$JS$gON`%C;allY0grG;$q?J!BMz|ZU!=Xt7jcL*A1%O z2=uQd&8x=Jc~;^Wqzftei!wF>5O$7(AOH?V3G=Zs=T}c-(4v(+8r7P*E$sJNNYoPU z%GzslwiaeB6BhX~jP6_@7~D>B2p|qDORbY>cMq1yH0Y}=sUams9PT?uOcS2{@IV5x zRXHKd_WX*sJJ+GN;meyPF}=G%B#HO7Io!aNRUmg53&wh!aCoko-&u}Cx_6wnkg8fO zpcM7XebRCm9&k4H!LF(jQHrsJSh;Rpk6H%j?BxKL1gOtA7|USej(^TZbRfJFMh-wE z5(i9=TzXehl4m@i(PT-JjPg3u_tlUnaqCOGde9-k#T&=1A&6TY>nbl2=@UefE~9KC zf4b605I55}%}l2ld``oRWi63r<5<(J25$8WNJp%bs{a5pOpjaA=0Nc3R&pZ@hFR2t zKR;thRD;=#!b@~qisC6$7^O!ZsKC^l>V>gWQPwp59@p&}we_u)zTm!X)XN&=^ci9X zYcpQ)cAui?_t!~1wbjk6PU6unQC*6TbCbXwN$deQIHoI~Jb0V-i_~>3Dp)j&OKm(( zj03SUxXP%&JPd=p=NLIrjtM_M>e|irsXIw^B1rE0iFF&goD80E$^8dmjyE?qskD96 zSc-j8Y2&ex*b=L5u@=A}o!BR*-X#5VPmx)#NycsCj1@1E2W~q50Fhj6!VO!g)ap)a znq3Q0-tJVC#R4=jhX*CH%2b?!PBIST^y34H<*#)Wj`;1y0P?|5075aI0O&dV{iid*4W3g_$4xg{(UW*2yr!Bp;rk$s0(OktlPA;_% zCB(A;2+WU#Oes=Su~OItIc?ZuAxErJ`& zXelMZPPmK8dE^38 zSU1k$G6N0W06YLWBpmNPfpw&4cC$}qBvzB+-g#P5iX}w->{tL1pEqd992Q&>b6Z!d z^>qaPUcd0|A=8SIy~{dfv=btCoTZzYU6KR?T{B}fdrAQz>u!j zXwhK0gn643`Po)=$_DdT{G^r&cMMiBT(ft2Z|8GKO+B>!t}J&M(&vt6j##YOT0O;* zNsNVK*a9!zWyb;xI&TPV46$>Wp-@mVv;+}(786gUk1i)AB@PsH zC!xXo8vuVkDrn6{aeF7P>dtzQvb)_K9C3r=4O3pa^U@o83t7<3lE9m2L|~DThLO}~ zImS=R)LiiV!%OhqqpiruE;Sh**$W_8)JSsV9pIe&-4(WwtU(#asmQsUysh`!{DS*0 znJ2@IHuCb~<5RPlk}La(tm3izBFZFE3lSjzDUU8UE&`Aac9lx>p9l>z#9kNEuB5YD zvu`_Fhj`VafCL+44=LVvD~2Erb0$bFmho}i)2(`YFUNoQCq*Z18QFY8@a~5Xfvq)t zChFfu)ENzoj2Oop(DLoLE(yR>p1Xk{t2WKPT_vQl*=W+mYZF0s{(YNTJgFzz6r@fN zj9`L^f>fQ%04wto&zR8XoLx6-uf_NNx|zYTS0#eCt{cl)4J}FnN;ix8{tBEkB|n_kVe|!sZKF! zG1Hf)f1i^4JqD$Db1#{%eS4>Bv)^gP_fCB-P$H5md)bT9u~1cRcnwtTr-TIF9P|H#C5jE4oGb8Hfauq=SM-73yRXg*vq$-`~CTy}Y*S zXNZiXuh@|o-#+WxyoiDzX;`xrRob8)dMNw=$F(iZ+z{yy+Ub`|^6pm~d3HD(locC( zQ;wvcm>w7k_j!4wpqIM0?frj|%YMC0&kyKU64+bn8r+DH$3DijmPSqcO14hW4%P&y z0Oui?l>(QTnMhkddg6=Q| z3K+3sfNlz1T#|YqT!2P8tI}MOYFxg3y?lZ#t@@s`tIm+f1E0%%YQ9iDU!}4ZKk^BW!u3h{%o87*Yt&wljhCr|GwH+S{;% zLK0E6(TDJrA+e5n0C*#%ds2#;e9!&?g3#8BR=TvmihDe3CxmP=G#N2mz%U>_#JRN;o)9OQxt!OrUEZ|)v@)-2E!3NT_PX&BE_$s^y= zw87Af8mVngMvQ_(t_D;A}ZcVINiB$4$<V93{i1futrCp-5u|%RdCXB*doDsX0A&I~}2Tp^p zJmo%VLD)XkiEw~A-NZAn_8{)(x32(ta5K=>V-7sb6h_EZEuJx&+MWJD_dOrLPpG|y zzvm^p>G5e-H$GL*m>`(Kib}GA703^`XFo7roDsxbS`9Qs3oCiq-uvS_v5c=i`;WFZ zo=!zl{v$=p;6pTsBD>bD{?j0~v}qAuKmgdKv7Gd-*Th+1 z`Q!{_W+a?5tWkgg#{iI|wG(!;99E4P9wWGt^Ig1Do9#wPrIa%6oR!>2IRKmm_0Jso zR7j6K{6;KV++4w!BZk&((j#&R#AhRuhUD-u?3{JA_cP||Xagpm%J-#_6qw|cTd|Ek zQnC_A2YyK785tac0IoajGF-`db9DlJqT$TDz#s#i#0(#7bK5mE;FU)|kxjc;`s{eC z?K(}vAqq;VSBY5)I6pDuo!LLcJAubSab>)UL3r0HQf51l$gFp;9+)E_VF3Vqr=AJg z<%n~I+8ilzI~eoovQKJ|*~C>uk*kfT11JXZ{pQFxP(b-`G2aT~UzIN{?QVmrGyTAG zwYcD%6Tu`L4&AGsZtC&SsZH$3Y&M-mZM?gq1ny2*{KSkXBx5|OEJ?sTlga5*-s)F3 ztc`>X3vG|gU|uy*x8~!f4>`_yaaclb!e?XQn~3hTC$zqW=K!-L(Z=#*jE#;L86c23 z1Yi-@il;A*uJ0|UTU&S#t7aKqCK5_R5tdPadB8b8cpMx6Yq?ERrlr(}cJ0vUwB14C zwA0Hx*M576CYU+i20$Z%yoE7?oQ2~g=bn>K){9(QMg5}!DlhMfsAiKOfZPl(-C{B@ zLZ3|4(w&mK2}hfuzWykME~2)E8D8c>K1md&2GYkk2LumM&urk<)OV1}aPX@F>5PrT zJ^N=pjds(ls$Sg=DMs6tGPM=W#1?jwT-?6MAVVaMa(1X0Cy#2>n-Zxbuu71g_Na>oE($T1A)oO z>M_NPSLHge&eyR(YIb(BwY{ypW;Rc>DPX`WGJ*4dvICxju;k|;^X9uk@X2aCu^4>D zzA+X;S1yQO5;}Wws2fa8E)#Y0Y^H z+g`r=O@c%sU%dA?y%=ia~|^JNDxfkU9_>n_!ja=g%w#q z3P=r>=yAyy>BW5Ktih=rPVKD@$Yp2YNhFNz$-u}Phya`ek<&G`2+2Fim|`s?k~sT| z+l|5Hk)B0SAMXif+l&GD4n4n>4>{i?47PKV+PkcB8^=;>=~5WbD=9J=1~Ze_BmL~2 zJ$!$ zR*(kC$Sa<9;ejN3`{ucUZjwPON=%dP44*b|InHzVAHue!8^@h*{{RGqKX;YvY-!5= zeW+=g7}J>^Gs?z)F1xYUy?R%Ov<*`8!#40~5Zo@44eW_*XAzYs_-5n+&GW{fGLzJT zd0OPga{E|0Ew%jDXe~6$OMx7RqCavXZ z!4monCAUh1PE3U!Oeh2>R@=0XpQ|0k@Wn+&aD!L7e}8|>-wyex%3ZDZ8QNZ-tm`%} zZKJeO7LA>DTXI#9t0{5;+!K{n&49J9PW* zjHmGGZQfocr)Onqq7kX<$!8l)8tzqrB1AvM@}0`7p7@j;3}o>|(l3gJ3|?79+7wEbJk*u!B^UocVywCmvquRw&nR$TucAW7HgvlM-pI zt#tS=p;9%7;oA{%kjEqfH;JP^IP#Cq%nOnb4cNv_adW9sPhMu)etX`^{jci$&ZyH< ze95i9uk$>>c#ZW>6(!aE#l5YhGRkc9{{XS8q+VmlMioMaU|;XIISbfebg}Bz+HSLV z;mvaK?QLSRF%Lcke=S?gLZOvOF_n>JMgtiN;aKB;uU`jNoj6jbc}gnx+U;ul+V|78 zosM-@>A&mx)A2mE=_9wim1A_dw~AA?MM9!P2ZMvi$miFZ+K$Do#jV2F+Q_r3mQ%|{ zr5Fx75%+=5UAe)pYNbaQCf%-Zbvg%DytcKt*KbqH@&u?P4V6H_eo{h)ASu8sM`6uA z-E?=fi7$5}uazW`$mqMg(yF-R?g38Y!sBrSk_B)lH5VEyXK%lDZkl??$JVv0NmN^n zGAJjtf#H%hl_ZU_!M7}H9aOBM^;JgF0NMQ*%5r!DDk0)gXPEx zz!^?%)C1#T5(_6cAx@+g9hN?T*(bDhr`5t`^DCe|OXzgud0xU?8EK)Xb zMt%{Ek|b zDMGAWDe|{{ywlh3)TZR)c5zc#nVQ|@bhd>ebr3{aHtuvyeJ|xF23Brju4>A(z(?T>&iK zNJKjy%Zw>uj1HYh+73@ACq3zq+rQb6WsL*0C~&+k zMq3M>NY4X~r|TbsdOnc940N4J!BR`hq(5kxv%Q%Kj02KL04M+sJAutcGEJv${{W#B zBCn~#e$$d^X5+(?+QkBEcTr3)nkNC|MA#vBT<89JC~R}EjQpSv0CGOh6i>NWfw6wQ zSpEQy#XF2u3$T`6UmbY~U=~t2) zb2L~dxuTaWj6F|ZzJQzPY?>KU5xUDCC=MIu1ax8> z(>QM2RMr~Zj-v`Q%pqxWh?&@;*J+ZzY7X<|5wd>md0PswwMY%g2Esuzx zzjTf%2c0J&2tAi1XTRykZYwL|Ua>xn@Y7nmwT&c`%UDXkBW#YbvBr9ZVn?{IT8(Jc zk2H2UCr!duX4i%98uP*0E!3kDPh$*>4`R`Rf1PZ3pGvr^MIlZ5%*F9urTv}ZO-680 zSj1ye?SKn>x%@Mndsl+$J~F<#u}fC6v9`Td%$x6}D90HrN0tEN7;N@Ch7TOrmCVvN zpzND7FFaoHwYfI-*kG#0&g7lL1F2*j`jL)L1HEeL{yVlGp}0AICFlH{x4OI%y?Rs-QZ+6rKqR7<1DkWZ+}d9DD26k?I#H z%GS`lxx={ZamLffLV6x^&oxk|t4va!hDM>Qyq9MF@`<=W!&C zG3BrzXMQoBy#YN(>0K05y_)9S9l9Hu3^xvLA-8zrH&KYTy2ikf^MH5opqSU^$lIKg zjMrtXUP-EpsnPV9Znp-yAc$HgFSlZ#9$^TAKlXq?6?w_>@ol>4TVq9HyJ<&T(bjRP z>62X8v%lHaQ~;oG81u?^f1hH2!vN%gXHC;|+y4L};Ud4?EE2A13cALgevLP>Hh!& z?lPw)lA2q8Uzu9xRJzo>J%4j=eZEh$ERqGCqn1=4CNfDZ36$YO02Hwx5&-3IV*<|N zcq0)bNb+n{;YQ*B7|*6S>0LM_%O?f0)Ab%IN~gcy{tEpL*Ta4mx$ypz2Dz_Fuz6cv z2?H5fzCg?6fY^2bt`v~C3=S}Bg|>T*SH;Z?F1Icwc+kx3=;JI%CC+xX=FV}!=e=|{ z&sP1Bl2(n5v%>nukzr%u`#nktO5R%BE#M8bOBs0*IJ1$AlPD*s+zvp^UbokuPrsZ& zG}76jmEH*5zRF~EV8s=S76Wu-25f?>lYw4FHxAw=OYF7%eD?fJB+{i8_t@TlJ5{lZ zRkFF1$*Rhc%@2|TtS+*%w)gWHVj@6ECnt1_%6za5K@?go#hsin7u4iOOMCA>VDlsj zQPr?PRNSmt2?vgF=DfElQK)bGd`uOH5Y3VM| zWn;0}a^q+qlYqP~PbxcsQ)(K%jbO4^wDQFRPrOZQ8aZg#qYzU&cL1w}E(nBahSj?Gs!PT?Ov6x(QN`2Q z&K@*Wk8FzbAD3VX>HsAhU;}tnuFBg(xYvSe`fjNldWf(|H47*UOiX@b>@k#MyFW8B zxnD0UxTqj6gNdz#O;(Ptw`=Wn(_?7Cw{PpYh2mK5tnNkZSF&u9KQ`UQBQ9dXBPk@J zf%0Wg=j9psf`P=`YZq5mN@EbBMEEV|V90qc!3*GS0Rw-ypdQE2%Oy&+H8?N$Js;!0 znlfvlL+o-*IhpkaxukIuz8+I86!_HM<^}MSQY4UarFBA zE7he@O<5~j@gb{N4C3Iv_|Xdkl1ALIJ%HeTSjAj|_kT{6XjIo@zlXNbae<&=at_ftB|e;Pm4s>s0O{W!RA_ zNTrfj1zBGJ9D+g5UOyg{t0Z>`8@OW_OmB64xELcO4spoz>-kKYwxz{&B8EuJAsR8l#D$xJzvR_ZcfHWs=z52T?)1xVg}yJh-wJBd zrJCDg3oOYLqE}GGhg<=T*(Yj%$7PG@2c1OEWaFh~IB*bWD3aDMypE`JI=KfpKb z3QKcw4Yj1UT71*Nc9TgAk2D~Z182*>%y?Jl7%I#TO95QJj#Exs9WL6&D~RQ{u~73$ zfkgSs7hXPLmM92p0Utm~sC?Y|e_NLHg_+G>Y2_idS*MIg_EeK|XJ{rc;W}rXz;F*O z*kp@ZKZ&(H4@jB~D^S!deA(7U7I!ZgAR_~i4m~>l6{T0YXu&Ha(=)Xj>#a{xlIG@V zC%Iy~npq5tKmZJ}B%A;SNjwUj<=WC2MhK5@%0BK!2XFSd`g_&Wicv`t+jnNo{=IyP zE-YnLlVMUrhh+zh0(u_&amdF?yJLR3uZL$hF>z`EP1*9|0k(s||Fe zr^>Vl)NWQa7f{k~wDA_7b9l=XOpzJUB4ho?@=J_@N$fW60D?)Xz7x}K4fL^G1`!C~ zdMsc*@~3ItjHm^(oE(q`8Qe3Pb%X!2VKZUk~7?nICGSaHK1j5!4SymdR9w^Z7*USskS`@}G< z(p$jNu#9|#FaSBp9eD$s;C4Qg$aRNf7GxqmLo&&namefG`TiB3vRn5lNZX51njoRA zHn0JtDe^ccY)o=G|H&|njOO=TbKauwQ=IP)TeU}R(rap(d1;<8lQkGl8V)g4Zs zuV33+kFzhA8@B})hbl;3z~h74*E!~?d`|H`qj~V+Jv2$W?rY^+g=oZPNLO=oC$lVP zBm7NvU@uf*D4SqebP&9sbiR zZv$cF5bn+~{%83J-_`_#g=0OI?U<4u=#VA=e=iZ8eE;Oq>+QWWGKc03~}fO=k=&=nf#_ic?$#g zG0=8C{Y@y^;N8rr&m_ zM2rtH6gv&X;BqiK{{Wm-Ta8cb*HgzmtfM0+BpAkW4;dWv{Qaw%a(A*c(kkVvWYOmE z;J4D!>2Gcfiq7#VjmSWE7A=Lw)7WEh$;CycTeg#;B1II}wzo{mu*3={m|0bKWj=qI z1{XN_aBxo^eBAYe-LQ?NuS1mA-r8&HnKa=N#-J*yu)^UKFwOuZ5J4k`JqgZEVgUBB zFds6IKypu9<%byS{Jyp8(@jAq*vZ)JJT_*sTPxVEO|wfVg=EOut_J+F(W(@(0}eUf2m0xCubM0HSuVLOxfo9;3khIJ`}sl%bjW7nQg)0x-!Uk0z?5o07exya-$gs2Nay_(@T3je;>HU z{TVIhk#V4XhSl|Tj?Q*l$lp&a#b0>}>V92^_h$?;*zL&`O)aOp)8Mhm^W>BlSqMiv z)kz;d2Xo0dKBOF;oFtT+lK!kJk#TlCN5tYyGvVf+5-KESBuVZP2*O6ns}WW$lB9_; zA8;d+fyo{>r`&2*9xJ#_S;e*XpFEK^V@Z}5Ryj~IRN;Z{eMWetLB>^Dmi<2q{nz|W zCY`Rf{=dkMWfQ^ScvOhwHu8S)o8`f{kPM7*$UOV@#dV8tnsn0rs8}YS7@Em1BaB=i zk@pa!5*uy`5X=Tf0UL5mW8kMQOX~Lh45Iq7&XcIetwDa8T1+pkZG8A(uqzQ0dC|sx zV7Zeam4L}_mGO+7-A(4A`e(w3;1&&WacEUZm3A8^63D9L0m_^@Zt4n!EW zHspXw1YrFM$9l|yHHT2R%OO?>2`AhW>(3qQst-#Uv`CD<+D8oXEGG)dzysI0<3HyW ztEJt%TD%`>vys**^CCCOFb~YXUZb4j(xOpKE9fQML#7FCe8r8RiI*lg$v8Rp{69QX zbqy*Xvs>Mui6dC@<8j92$Scl%XDSa~GQ%CKIAS?kOH#Raxh>AM4XwAAX04fERhJA3 zG2ouVBPzHU;Hc!4+-ybS3+tIy%KBMhdwiw5EO#Qx#AUF3eR=4kpagTr1f?H+>vsPD zPr*A4$==44THlFVN7P}rYim#K8|3>$@guCo05R;y=>Yk1qyPwYR!g zOM8InD=22llY$OTI-ZBQ!Q|!Az+tBcPS;v}$t0Jj^np%LcSPFWw{xw$S2sd6MtL90 zk0C>`%brQWI3JZziuspLf;pvHS{mVWevr?$z$@ACgqV_ZsCX>e*wlTG00nZ)n-O*z%XHgPp>EZ;sR-;LQ#CvxZ~=flOy<8?k~0MhNGOG65qo2&>Qc z#yB4-t!c%i^}qH0VSBsS^_yGk`!9exgjPvzf3!7Qct{Q@6vrG&Jk{eP zX?S9ALHUX9F`o+YACoSzc5URkawU-h%@|fh0YZXuNjMlI0~j8p38vedy6#;??xLol zExhsB7~!5bTUdm%yt|`fBTxWPnNz?VvB^34MOD-MsIFy_;T!ByB!NbE9OLB~jFQ77{{Rmi>&Y(B z$$jBfByc*CGswG${kNiy@1?W-9(c*tH&T@5)1=_#(FS3bIxiZQZrV$oT$zZZI0^QqqXu3GXiHt zb&~4m9)BfNfWIoY$&bE9;4_RG(y_Mw&t$oR>24rU{&F)g-o44^ju>R*jN=67o=TUy zDJ67jn~Qm7xw*3P9!Zio(a@Kb2LeOSDlnx-T=|DM;MMz!NN%1fuA=gM+{qA*O|2PK zTzsJX#P>PqtUrhm%MNY&{{X-?jh@FgP%MjMZE&`e`GiOoZNxS+kV!b&+p&(|`U<%W zb6U?Nmb(C?KwH0NiPg+-zTg-Qxg!}HPT`Keob;_H7qVNKIGAm$j6$<3U~DEpHZi%tle=7R-z0V}a8@r(^ytsUlRaYuxHLkz1@$+sPzuxiZK5!hHAU6DM|E}zc~dct60Aqcyek}zGmq!TLs?p~i|07WWr z69a*d$B$mPB=SvXQhxh8Asa;;`%fe?v4z=C<2z0>$UVKs`Ndj`A`J2i50@vm`TlgI z3vxj-M$FNw1Gi`dXE_|7uU>yDs{a5u!ne(gaxy9q%698Cx}xp~+zev_9eMikT~3-O zP0buZQYc;Weo?dnau48sq}F(gI6IdONlk_Rvm_JR+AD{VltyFn_mDU2PDV}#OymrD z)tl`G811zg^=lF4@RPD!DumnfGyK>pcrA?dJcE*>$etsWaz*NmVGFN#zPe0q;K6GI z5v-BPG{~rtu~~A_IAjV*`EWQmbwU_`Y6-kA4TY81JLCI2${;&pdpm zIUs=5k37@0`xj)r=6Pp`Ad=f$)Z>w0h{bU%eq1Ny`A#_CagR_5@6Bb|!UeofApr z#Fuw*O%9k^;TT()SmnY14&X@*wTxxgra&1cyv##RZ8hchm1T0DgH4m!Y8ut?YlpVC zWM8yA{HK#JR4Tc|Zc8a*!|s(07;l?fTGH0jMA7YIhwV0YntIK05sIbLG{?$7*bGX% z=NnXg=Ny%4=d-6fU;6%;r8b*P$aqc9CsZKXQc|sTl=;IK~en+s1rV4WwF>XGii@-Qteo>MLeg z(fM{^j!G%QZ9)p=lROvsV$XhH=M zl~z!}M%8Qrcmri?nQhp?EVigD;}S_7>!mcb4(aC&p}{{SPN zQf=7jyhWqk>Y6@{1Tfi26WqM^_jax zLah*o=IVID;EcBi9DiPn6Sd>k%uU^$KgO%uqoqPO*~?`t4?8eBUGig8BRxnw@JnE@ ze&W2F!SODmtXNvziKUKtLbAqwcPzwpKjWWF*IgK;2QH(NeD*Z-Sm3$Vd?uHS!Uwj) z5Zg%q07#d<4mtLwc>3sE__M@;UHpTrPVq;$6~K{#;0)sn#|Id|tz9_Ew=@2_nZJb0 zi&45=avO_R5njE=oD5MaDQ)>XKmY-ezyJ=_*XtfTpIS{%PnHOouGJl`qCpgmDulsx z8CTi?Mc`pis1?JDz{aIW)mz1V`loNfgl6o~z&^1vM;*GzBT@UUz!G@Qf0!SJM||fS znNkLcSb1$ABp*;sdNk=M{LI_AVn6k8xW)p2PDv*@Cye_M(;wEYyy!1xUKQW&zTmq^ z&JKAP&NJ)MwB;4eIduoMjySBy#8}%P11fS3;Ch06MJ}7EyqC--MSa5uB=A4Rvi1s5 z*Koe1wmR68QA2feaTG*^#9K=##|{Y~aKIcAIPN-zjXL7u(Tq~ETFl$AAp>?lz0X0O zzolo%8ZP$G6HO6Y$ER4`sCfm@mnys{P^^rkkkRU^y$sSHf1D5J@w;&UMbBtuw%|14cYgsO04-(I@!*nA6;Qc^7{b;(- z70!C)3`~p(4#4j|=dDR&adi~ND=##={KQ61NC4-jY-cs9N=s5M)6m(!mRM{dduy2z zIM}WMDBuR%yaIhUdlQbpoWo&pCYK(mFe2Hf@)kpZ=12zO26OR91y^x0x$>}4URz-&g&P}vT5)vZyS*3 z&4h*o5KcKc$N=Lx&u(xD&8N?ye9Mx=8WJy)xj|MeNGuLG#s+?%*42we8F!HUfD)vf zcPIJ-_~Nv89W=B%pBQ;3!JZ2+jlM)TFx+jzjP8wL59v`(N>2VoRZUqHu0GQqlW#nT zhSoq)0FY#rRRopJM^FJH2WbO4P9LYm4a;847BVdDgwGs=;!m9yZb1i~-p0CmvzA>D zX~69&yGfK_4yQGG<%?Vv$I7hD88`s)*ufot&!D2(kL;eWsrbHmo6FL}!)(A!14|M# zhvpbx-Y*BJC3BEUlIj5*Hk%xO+qaYIuz}}E_OG)?x*)BVI7LjJybgzf>t03{kFloG z-}?UmBeK3B)~Rlt4eL(_*GjzGZx;EeEd#5SWe?h%i>e&#FQj1XMt(?rOM_5Ml_8k|(*qT1MT zNZ|D?9klD8u*VsO`Xyz$jhui~0N7x0gY@N5J<~#Ns>&jQZhEL z)Mx9D#DmwAxVvgvx4vtM8FL_*@bbhn+l~SM06F|Ao*{!!zR)9v(k5qJ(j!MAf-{9G zMtWmBV?TvLij4K{T<+7cYSUG_w9?|SyRt^|B3NUPpjR74Hy%zwI5_t2+g*HJaUO*> zp9Y%@Q!*HZ@7i}vib=r-W_JZ_1CT}m&PHq1_VAlIqfSvdNUUVFks33*YXVOPKZob~ z)`YRme;1Q)9_b$;RAU?u%CePMEpBY0_Ay^fa1K#{Ju}?WTiZzus|U_h^v-yybz&=6 zWT;?-yz;8H;8~!O zmdarTvW2{#$=D-e2~(6V3xz~sv?~pdoP{Q&xzc95h8xX3{wr(CNM%E3dvs-Bv8Lrf zrv$ku#gR*u!U7L{q@y2)T?lt>%;NROT|-g2Xb=Q>9gr#o7~PS8q=Cnw#Z|XoI0ak= zUYYdkUgTAvlITV5dpCn%NMf*$Q&o=IRd}XG4rI&k`FiYFpMY7Fu*g1Km0)Ri8ay5x z*5LBBH_hF=*LP_d3kiWtzGChSLEe`D2i!hVIW5nLUQNmJ_bS%aoXxj}ts|0Kn+q$c z4YI?vFRmb6>dEHFo0R4P9Zu2?FkdGisoavyY!+|qOSvc1F08}`<)M*HqDrc_3cg_4 z&f66EfMvH!$vrZ2=1NmKDbP<;Qj5aY3|`nqAuLrKQ=xyjig%*M0){hrWCn%h1%)2 zeqFd300E5Fm_*ZZeT;3Y505uj53Rt_q>E{BYZM|za@)!?k~eNDa;6qP@Ds*)!1zh# z!{d!OM6zvXAo**O2wa1X<3FH2n6AotQYrrc5IiYpw_YUhg|=BkLAQkjj9f~1&wO{| z>N<{2>Qi{jaVx7!HNlaJj02pKaoV^0Y?ts7odRhe%ok-+C`5sbXN7F>*Vz7boNQGl zk^^+9xA@ADpHYs~<5S3qQ?|DxO(aJG$t2@=4Imr-Z- zMO>bN80tRq%gxm!R*pp`L5RTQsT;WK*!8OLS&44aI8tdO+C#>x$ZRqGLqDI^morFi zH)XR9E$GR7WHDVuRnvIBwwlb#4UrJlUT_)IDk{xbK6n0`?fz%E-=g|E9`NL~fWJ)ph0?vfL4jXYFTBJ!! zq-cpk2*WAH4QUOCdzUA9zQGc-k}y|(7@Txp@E?$>F@n-Wqp{E7{zW(?7}*Y2Y~HmI z+gfXp+)D9GAqg4`;z#q-Dt)$wJ-Ja?u8%88BW`Cb%*3C5YE`ds%KIG-jCe`&M^Z5bzR7-bbqMnHw`sT7V8(T{_qg1rE5;7wx2h1)b zVsd+|5u6NTCaCB(R~m-79leZCB+O*n<>MfPkcQ}Uw}aB9KXKf-FMRdS+84!kBx$;K zk#QvE@&vk(-*76Cyoibj0ZQ&Oaf7t47|%Wrp+hC)#xW_6s&5a*bAWT{l0OQJJZ%T9 z_x}JQt2@-OLsEIHthA8?u94qq=gn1Y2Lj?jgX%JX{XjKd%HX7WtZ$NLfJgaChyMU& zuC=+w+v;>Wa;zF>n9Go)AE)?t{A!!>u!hDIAm_dqjz90C_|To*i)W(Pok9!v-uB#0 z0d>kujkTi6yM#C#u)y8iA3`&|rQOdQdUQ8emeAVg6B{(ZF*nNPgA%9ZJ&!wAJDhH> zBCK!yc^%k!sHJ^8{{XL%uVrsxe{T}b&_{Q00|{0|#034#}q|(UJ5XbL2G5Jmv2*M~JsUwxHDDx_c5BmPU$my=4={fiR0Ea4A=@+k{ zUrw=1l3TK$C1GYV?jUo4@_~+xgOlF|vt+%MUE^TwBC8xtg(L+hC!W0s=kwyXk1a;p zJo=EGJ6}tWv%6GZI5LS{LnHL$9&yJfAJfyF5A~CIs~-HgQ?Ou@fKP5uwmW)tD#f*< z1n&~qwzt$Zhnb*-SBrH=7Ao{n`e{Obwtr z4spN$kVb3Im1#oG{{Ye{Lz$e-{{Vm_i%gRKEBIz;-2{7MYyf!LLa9DxN{j$NUW&~bx?F_twYp5310n!6g2OC2bw5K_ zmhuw*R53dr;Q_FE{XMH@+@)hnq*#pXAhw=m&jZYpEI%<>H}b)2KE{O+f%$>w2eFauJ=V<1&WY5`_M&;Bw zh;9X>^2>a!gS3;+@bOou^@>+t@EIJ@wb2cOM=W>u3*|JC#pgx2ipRG&IacR9@Bu$E z;O8Q_2OC4HNWeZa zNKi=_86hE>-p*Ua)KEubzIc3-DDq?Ud^L@t54IeirHEQw_Z_IpmxKZ%n)zq z4YUxs&OryT&}I?qej?K0w;?3B`&zJ3vN)s>st%+A!FqA`cmSL(3Ejyq->I#(8>(2{ zPvb3PP73!MtddlWLUe z9Tbl;HX{d+xC0z$+b{+M7izO$>pMg=Whs+@;3iGlR9U zfrD5UR`(L?dOgLJ&`TIu0~q8Z3|r=PCn$dI019{Kcz)jQ>s5>@B#$4=1Hh~R!vI4P zIRSfQkB7d)l#X_-D;R$qF5;KP`fi^Ry0)Ka5=i{QRS9Fj1e|3;pKwQ7PlYjV z@lx$l@utx!$X}EYG0^om9-h5HuDIz)7x4#p4%|uOPYFcs5uOr*lY&}l00$iR&O75B z>&~p=Ep1>J0*)0!V6m7{H1~bzn4F3T2>YT7VrAS4}fZXrXlk zfRb$dayoaeLh&E%k0hcp7${R5fq_s0gU3Vi^f*4klou&=Wz3a^Nu%AQ?K&d7EcrEm^EnO}kkSu^Qh8uSb=RV_#u6Vr`=3K2ZoC_IJ8DHk(YUiO77fcQV8R-e~lK<9_6S@jXL0m z*j$W~NXR+Q@~L4F$Dv5bN?b>Dg;`HHD#H~oL_N&Yb0WwXIm~OYar`;yQ>Ild(M<11CINXTX%giO$(i{hgyS?t?hm2Rc%t2H{7K>jKuSlcN{U!ufFyq2yjC0T`L^;Mzn%%Hj^#}3 ztf5zi5OBmGMqZfUALm^RW#~*Y7&~gtQ&kx~v z6-2jgV=-GNp4+%A6z-m#WUah?h-x0x;@S-{tEvK+`JxBUf{kv9O zlqK&jg7Q_!3-ltoqu|q$OKn!h@glXkQDUz=aoZG-uJ_s-%aDwy04nVRlDua$9Wpy@8uIcO=dz5e zq|DsBu!nSGmMpp66a@#V1RSpF`E^!Nl8bxW`umT5RG}!jCAI$m5BOsvSJo$%d&IYy zX0*0(GRPu|IRU^>cc9?`k$5M|VZxobt*r;du8axhs} za*w_M2as}Vs$u=a_GWak3Dt4E(4HXj5=(F)TisUS=V^>FZdmQrP#h|aq?TDIy=JcDzuVCwSRWIj~HfSoObRu_0JcDQc69#`f6v3RHWtIZTK6SXwz-< z7ilf-4x+OO7B^BABN!PWusXj^N!q^oi6NL;t;3MW^9GDF6=DYmk8%jkah#0sXp@t@ z`n?PqPAipYkvvkgR|`Bh5-ib33K&7iRT;=R&lvW{^1pAXr`jAz=SgZ@E62*=dj1@g zz`+>`FmNlHadLiUN!xO3x#YXESS?CK@&+=;aS;97oPx2r7;Y5xY%$sn02<1Q?(*K{^vNzok#b`b$0y9H z>N(t?7E%bv=vBG{ipg@#JL{$Y077kRYHAx@Ldq-YU^2)p{#&(wFiRUdVSLVWoyo?2 zUz`GW90y0JYI@Jqyo$QBm%$?Lslt5O~31)-@0jP_e-kS z!9Dft>m(2}%Pggsd5alfK2&^y&dx_qmO-52vaWS^(zI99E*aggQM}kNvU$=rLdr6B zxF^kG`(KFw#z~OOAf&lvrQNg~wAC6qmxyiSxVgXcWsHI5+caffRgypzw_t@r#w@uC z!2G9xz}2PHEE4f_`&74vH?~Po{f)$g1WoQDQOQ9Zpc9ZbfH9oZI3)D+{=X8r^eky@ zr96?P&WmY(J=Lg|XSih&nB~URPE;{fKm#ZkTxS5Y9E+=KeO7B(FW>zQZl#ji$rYoL zL7kC#QZlf^F$ZZ-F_OEE7Kq6yd62Krms^(R-&)k94;=R5<0e_GW6O07 zl3U#jh57`!_MWdYo_M{rAHAD1=sEX{6xE#X^@7f$$bq_u+mu3btQ zqP}w@Dn=rfQRNU0;g^y{0e@wWTNKdT>{YGQ2fs4eTOGV3$amQRl@R&e5v?@yN}!cB3bj$f1E#n4f}Mi1kTj zi$+P7;{9c_yAk;p5k@Vq9dW&uFwC+9*aqjxD#{M*v8ho`c9y0QT(@s@dVY<4EVkCt zM+MWz_C}eZ+(by_7Eae7^oN^L@fUm?cjl}-;m02uW3A6mC188SIsl1S_!M3&}I zv2Rg=n?c-Ek^#UyGsS7Glz0u~z(+u(*lx({*SA`T)wYb*RBx&yZ7LTSXb_+7vB%P= z&*7^zECevFPhzc-J9Mo%%=rw-d@W|^T05xY--DgG9;4;K#ZtWR!z4)SBu^P)%BbZ% zGB82M{{X6l#NeKWd+Cez!z2O*edM_?7fXXFR*dru@S#$f<$MEbq$rw4o&eSy<>$$+Yo=IRpWmHsGR4l=y z;9xFG5`FMJ>Q=J2IKEKStfsXqcM3AV$13jvr~s3Wn07$rk0ApnuadXNv( z9qQ^g%!SQjJzhOF*HP4kZ*Fg3w^*aJ zm&t94puXG;a7kcDJOX&hAOgWTNu=3ay4eeJpv8ZoTHmQgRh8glyb`1DC(GBIzdbmt z-}pvgeBqc7e}rfH){3J}#VSdwf$-Ov6p;yE5#Z#gIo+TBC-tag@X3x6(a0ZS7CWQ# z>yCd~YM`APmi$MjKBwa?bs;OZOXs*Of4W(Z=kp4zo*HyT^A(&gbr`0lZ3OA_7huXn z!3o`l3!cPa59M3l7-JWRbjN`nRujgn8a~~}90Tc{*rk0gYZSSxQ`Ta(O?yyFIo9IR zJDDSn;xmCFD-aJIK^t+~itj!Rc@TKI-aFf9riw|RhYz@bWROL;1?sq!(C6j(`4lC3 zYU0|V=UzK&-D6p`6HODOI(?$XLk*w^RhtJmIe8hf4i4@(#Xmu|bEE36?Y+(-<>?U(yk?Ya&6 zX?8KNmdV1+8%-U(#BrFj^D$ROF2DeKoCY~iYvrfQQW2ZJpOyarJrARTaFbE%`Tqb+ zlKNof1n`$~bDgaJK#UYLxj0IrF2O_xXwK+r)td83hkQLMt zptnGC>QCp>n#!De-^%|0*O%gHiE0sCzdL+~!*o$(F5_|_bs@fHQIFxNWN610z zhVFT9ybdY1x{c&8Np5chmf_5-&@m~?mE1Fqd*`prbKbe#7N(+W2X=G6@Q_H0FbvKE z071tqpI+yc?TV-_;2&svi>S~B1O{!*At$2a&}5TxSoOQx z@B8ndDBs@biK5hEK7-x7~0W=zzi9A>cOVDR)r>F>o>2h_^V6OEhUn9 z>`4D#kp>LY5dRf%0D^i-E~6y?v`*TzGbKs;%AHJ-Wvg+{nwho6L5V_AuC+ zrGsdHyzDxi`L?W*x1Zt&@!ee{bi9fr8^evLoP+aV4yPjr)~wkqW#fW2`6CLb2;}1& z{dmoL%$3~szYPUG7PPkpBwJG}EMduK-88$~89RCE2vT=~0R)<6rZ2RnhW`Lemg-v= z;B{E-1f$AiR3Qe{*~*qA5uJ#~BWWkVpDLWM-JYUN#UG9V0UcI za>nY?lIkZOWaV?wJUypbN8#--*xlaSMI>>(xqOK{tg*v@a84Pqk$_yb0U4;5P+c>` z(AmeijrNrsM<^q4iCiuQa1YIc!65V);-x#c-pspNDH3UVg~$9R{%zDDSv2L2ZO#M; z-ezP*$jDVtSZB0@k-S!3qv8ndF71wsX>aGnB*qwKv3R4{$^Nhv+{BW@kOow+J8`&D%Vx5a({(sj$Ey_fAa+N7=$TYy=kkx}D~vAtIu9iSj$ILPdH&x3MW!LHxR zB%Is4k**ao9a;WRSRM~U$7OHD;p@(BaeWG}Mi7K1w}0ewz6R7UZ9GNc!(;ozOQ=kc z79dHo;opu%TLj>B$pB|KJA#A6v4z&vA;eNQ5!@@GMnmO#mj zFdKXN=Cx90e5&J4o#$dg33I^8h9d(Wx!_bW+iEu0HW$}0%5&x7QW$#-RXKuzpDu=O zHmvGhqvMcrG23=My>t21c=R8$&W2?3VaU$m^WbARKjYVqX-cANLBV+nm%XwV`eaf@w)3sz?%G zj0QG?yEr%)VUzTv*L**9bEDhMW2Z*330o2&jZS%DyHw?|pS{s=7t*OqT}+A08qyy;FagN^*oN$a>@ zMhRSbgFNziVmRytM0vTj*wMxcI&NLj_2tpD$u!lDK`Tflh{hFT!2@u?4aoyNM*!tc zK&{(f99!yAKAie=6U#B$j<*E4Vlaz>h=7(=1p0H-bDdl}l-!flx>$(9Jgr>Lg7u=i zxmVNfzR=iF7ulhBkra}oWt($l&pw3q;;*HKm#hh(oiD5;TX!N^?V^f4n8rh8vT`s1 z>*-70hPV8~NpvdB;Y%A$ch0xGS>w5Y$#5=8umn1Qp`&k=h&cl-ft;QVDodovbQXOY z=G}K~^CB&^MnK60+p%JBPe2D+(MnT&jO`eCo^KHS(JF9Nfj+-~{4-q#g%tk)!b7Di z+uBE@7X4fRFZZkR3M27X3G*dIW3+&K9$>i z8Jo!Q=bmAe#l5-0qm&7mp*z{Z0YKzr@rK3@XiMB=-`}w&tKs{5eO;7GX>6A2kfpuE zl1$MeqNrA7O{awb<2^g|uP=(`dwl}x;>p!+6_jpak%CC+`P4!4EqsccoLg(m-|)S( z+U}Jsi7uZ6I#e+K0HoYT{$xKh58dqRmS9dmBWNJ~;MuZ^iTqs&zMe~z^9)~UbY%rX zoVtQ@$1Da$aoVjX5zrIH7BP^)vI!U;HFWmg zBN{V9XLqSg(}H4eC7v;270QjQp@V$P?n0?f7a0V{UQFmk)8%W;e_xs397L5)X8la} z@sYiYShh0Bb8~+zaYiG|Vb&*8A0Pt58@n&B1&w3eUH<^ZI$^kv%#cV6M4MG$i{{#j zGmv*X6T$n#JcE@QE-}}=_35GsS5kc5O=;`?Lil>_-qbo;tV;7LMjCP$Cm=ZFg(^-@ zIUs?IADXQovYN_E%|YX}YiS9F*;JKveZ(TNj)3I+>^KDc*&>OfJ6Vrh1A~rw4i0OLxw%wkXg4`xMn8)jbo`IzYpV|#sQcYX zsZl?XMoXVENwDsdAPmT-h9n)|PNuBb+`M;?T*V>t!1?2l2TUB2-10u71Y}^>T%pYu z6P_~VYC|sPxQImUGD?e-EMJq4P)<+fjw%~#yM@?f0Hkk#vK-~T2leN+J*l{Rp?yn9 zbtThF0CKW(v|w#q;NTA3O>h4IWyrc{nr9bwuXHbUmtfeBZv3fy45%3x22Tv=y0yQMmvG4y z&9o959h^$7BxPDwjq~P4RHC-Q_d<|C2X_XMgR|Gq@%+w;P>-}qZ$h;8*Haj86HRrw zx0U6NMbz3Ws99fl9&6=E185&8$=U(Ru&v!U!}nTuk1Qv&Sp~###dR8ATmJyZt}`N9 zw2m8(lziqr*;a5y7`3@QV=b5Uph}X0U0Xc<^T9gg9xlJMMUr#ALb6CO(u}7>QpBI$ z#zKtboB&T1-*|G^r-N;-wR^oe&Yr;8N#;B<{hUiH%PdY;G7x0BW*bRQAzNw6*0rXm zO>*g>l8-LtUxwuJwGCfTOF1F5Tg1DyHoG91;#HBHH_A%yh1r33Ku+9lBx3&niFJ$n zsTpER*ww83_&(mo6oS~qVhE_tL%CBa+;UtvWdkG_yFp*AF0wXKPAm;$KbBtr}H@22Hj2!J{v5x*B)^%N0 zTa80Z*>f6~it5UBkw?s+edge<00e(|uons{D~D_AiQ}MSsx7^<4U~d0aPf%bbV;R{1psYTQe+!IL-TW-@xe6Y zo*2^dEk9V2{IU<48KRuS!C{s~;mO*)aq}FGGOL%CIxp+wK7M(7-!q>0r!~YL9kmzN zi9N5|3oJ`!A~DEq@PCUSFu~zMp1ICNV0<0EOK%ox_V)|sLoKr&-AE0)J4sTfoPpOB z>fqxatIEsz(DSjJ-q~IBJtAu@RCs3cOBPK{WS20Di5_^$B!nxr{uOb+0Oz2{z{frC z-@;cu81Wp2@+4a&WqW(CFA_4V;fKq)kytXT0y$IZ&2rJ4BkeWSv!c43qXlgeDSRuF zQJ3M>%yzIO*740TP||r!vb20EILiiLm;hrq9G(SsmR?S$sK;+TthSbmFQ0X9sKl;^ z<~z24a9yKglaexdQ;(ZGY(1tXQNG{gjj+`{o*7Er{{S{$*6gO$Z$z37n*uxO%s7!|w@EGUq`47=@{r9uX*kFwBi_W5o_)?w8RK}`+{Eq~jH-!(j0(#U zX9^c+Q`Z0t9G{mNI45s!ruH#(brJ5g=Z|rY6u69tLR?`sZej{$fFN)Q$Xsi9d%wl@qUF_3Nx5<*};9y5S8oa1rHJf2G2Yo}`wDiTTDrVGt7t?jLWn8LCw$bp#% zA2BVr9XSLO$j8mpT(_}V=8iw?+hi&}=mawz*f<_YC!7r9sK!CYAGcDyp5NppMXNJP zD@pvfg`UY7%Wr6;KfNk}LEE<{=EGwph*jskXGpg97xTppg`}s>GD{uYazb`so#DKr zleC;3IbPfvUfDlVy_AYqS`MEUppe^WmN+semSCPRyLSA=M;(4%3hwpBGIX!f-bIQ_ z>u)ag0xWkIvJQcW!tTa85*bfDKpCg)p5pde8F#u4pFOP5CY5AlVye>H60U?0$&xrZ z+Pn}!9P}o!pzueB;Fw!Oad9o$NtS=LNho2phSx$4eq44K<0tPLwO&iD%)OF&SeO0~ z9U|r{nNc-486@DBP)hG3?zRCxcN~m?`9Qx9w8Igd-%tcZ7GN;PB)7_=a2)_V7E#oY zP6o=np2j}P9Z4=e8R$FbR^F(NA@dq6D=;N@o!C2bjz)j25&SsPjkMGG(Y39vRb@t3 z3Ilw@IlwvUGuEwFeL>J&3ln%3PJyDCA}<)eW!$Uf z_xV^8k>9ZDefazwYk3msd6g&drIawan(Aowoc!p%+;++2@IAe2pYa8Sm8OxXyf^#g ziU)Kn7IVkTpH6YkF`rtcRlSg%G`DAp#!r{GY5n82!|Tq_fAQm89)PNK`!$rnV3y;` zV{q7wLxCU6axlL!&unM4bv2=cUZx!G3^1(V>?SqHl6f;EjxwJw$}&ba_ao)> z>5B7DiT)k3(R?Z{ET#^!OsRmF!O0^BxWec0uP%*Q#!-vgW2&t_uq8s*CJ$ z(5B@FZs(ERJKkl20X&6R4gehq{#mLMs{M}&&J}}5cN6~r0ptAiuEn*CWc592H@MQg zJdul?OGmSc+DI14@tWQ_)tv7M_f|j)?reegx!?kLTir?)zw>N_qj^=H;g^A)o3`~o zE_pe~=a6g8rtF)y{1a-@varoBQn#?bGhZvoDUS&d0EruLzbMQ%GW@%^4V5GkLk#jk&N;xv%UN2+5S623%;`*$d638R2=H1&AwL1nq38UH zs9qQDawuC6g#nMzkAu>&xPLGvU9{Mekg9OompFily*FdJl2 zuDjH;b{$C1ZVr7hQOkK)I%qvu6Bq-w{l`Br=iAux?M=9r;HygkC5PW&c_TQ-)2BYw z5`~&745{{mHLl>rhSShseGjfU{Qm$7&AYw2n(4E9#j@3CBl&A)*xV7<0WjYdgV`>quM z_2d)oef?@X?+*BGH)o1RM~jzHGJ;6Y3cGpd(;tsoE!~Yp3u7;b^y|+$YZ-6b%PX~x zBMfq*fwZ5OJc0-(BajUypCycT&_FE-k_UEKMiGwSnG9F&44@-9##mDepD)Q&M?!`$H~i zkOH;|=vRWg6HT2kn~Jg1d^7QEJ}AA^?X@ixKG~(jvRb5TvD}BjNm-v6F3MF;0Pp9n zdhZusP2w1}O&0B1(O`yYp}w^Rl&ocuV`0dTg1BSXaUc`Lc@^R8>Lj(X(-+Sajsp7E zSURj)hPP#^+Xsdt_KhGTB)}k5a>M1$JiISpymiQ*PSzUJ!kSgZ=F7oqk}$fsNhNHT zV#S?DJB|TH;NmiP0gm3yNoj4)m`j!2bYCLdYno4$rj@-vYI&mtw-uCfY{Dh4_^-d0NIoEJnTZYA+FXt4Npyy z-&44~)2>9D%BVZk@2Lu%W zs0ZcFI2>eGktGQ?bz{;_R!g`1IhWy&4%~QVCDSy?-r_mm%o0m>V~t1(&43pK7gYf7 z1cf;O;10QbMPmh>-kaj}u!qlke>PGZ?v+BU14dYop-BgfhRzN%t|n?uRhIVc@-n9u zxoK{%{{RHS)0|5(FuS~eJv{m6fuxwDi9#Vj#7L~nSp2Jkdf?=p%e@xoM*AFY_ZPMV zyGwT&jg~$C05NbE=GlcTNj%}3{gcN{$t_mD)}E(*N3NR}ENvR&$-R#5R<^fu8p&^N z8KYz7vfzxjQhDT`G=ZO(nyYBgNp&<)Pj?JnV6{h#1QML!DP>K~k@rfjdH~z9x~DZO zDE$8b9<0Tv+Rgs}TNN~&UEf!@k5IdT@8xxQEueKQMi{AZGDzG`K__Y5pOtH3;?*E& zL17@q*vbAHs||*oadU-O%#g;@H~NsqHY&>?D%>V^;I~qwU}OsDj=En% z8D2W|G)=9)mhi!EB+V%wyMU}~0-T1!6SQpvoP^E@!mm5B{>qw;tu)r8K=!~1e8(c< z7jQ^`rr?UZS$<=d8S8)rQc~t&&YHV5UeYLM%wA)xXc8#aM~J2gIpMpOW(THo!OlZO zkQqxXcm87*$xk&D2Iu7gfXB>wam{J1wK8pALq79Pwz0J@F;O6PcnP?YH9`(D#Ief~ z*cj{h*3?$zEI@+U+S)K0IE0Qo#9g@n#6Ua02XSRA0Kmb(8+afJZthG;u2Q5jLj+!D zm2UCeNUW=ktnajk2L$dX8P0ge`&_d}*x{I>sL$~LGDaAJ4^dQ` z<+`w0Uc!}G#lf`uZq%2{-vG~&#IDd-FHkYZ8OT1(MhN!ViI~dn$7?R_xx$?1W^>8< zf}@(zOPl0m)r>ofzb+tcr;)%`GYg63Krl!c$6Rtrz{WYMbKRT8^5l8q-WZc_c`zl4 z<3BSHH+;D#t^gZGO=mexy)*^Nigxz)6U5B77i}yIvAhyommr?x0#slTkC-2rV<)0D zzK?A)NiI(~&1-#W& zUPvr+wR*M-C^#Ln#daPJ@ZG)jh#n#@XiV%3cTu!oDxxvN;{fEh-WcP*O7%IT7;@iZ z6t%hM5>6fjbp!^MFreeM0Uq3g`B$iX9=VTC@rB-^@WlTBX0qNIRms|JU{=BY5bRJWZI0Z-dx~cDdJX4YOd1PUp3lO1w^Pf&@LgpwZWJytC zbv|3jZS&6r63ROD>`&=SNa#CiGAlTtP`6f}xk6O5mmW}XJp+8%RI!LfYL+u@ zELg14mJBhD6>-ZRoMX2qjKr*(dz(_H?JkqEAdW}1fjp*I(12A3MC9ZWSE&Ggm3V1a zQJbH$qR+ZR`{5?lzyJb^+@G88U}HRV#W?dLH5OR4@QQterlrXqeZwh+s-0;|YEPb3g4!l_-ta@ViwdlV_c zaa{e0&X<3rx@?v^+eh-GmKXB^xI4ED6T2WYF~H!;*gWy4J+`Z{E+rOMGfd8sJi3#p zAqhhm2fCfZoZ#c51DTv*H_XPHrrO+*E_HdtJM^tVyzo{b6rz7H79eAx`Ba~74+P)~ z3wx->*LN3sb=0ykSR|TTn3+-Aha5H;Lg7g#Jdkp5!wy(Q@}^%c*E`tHys*6*$&Kw^ zX`?Qa-5ZU;pK2%>-oOyZ%uY*w@sRR?jMh3@NMN;_N3>w>Qbu%1IWM`Ncm?;80)k(m zPyzYb>gnwDG*x2#*1qxAUK0CWr>EW7=tkm87{f~kmX5BhpeUF7$_Zu282JVd%D};U zrdVn>=4+1&-dqO^Zf}`Ec=xbo1%q;TxjWeBZqgg&IBCjG{M!EjU)S7Ew2irJV%>OK zLtA*_)buNxC~g;PUXMMtxn(&7kCk?j$ub?e;1&kD{{RWaqfLFM%i+5=w!cTq`AABt z232(>{KAKR4pedFo=!&HTd&Kb(8lz6wOX5ZS_Z2guC_NmU-rGkQ_7c0Rnald0U(3R zWromqN>p*R>YN@Id8{GS=DxhUlTcWeIJ~yAV$JQ$ruV0;4#lNmh9Qr zwvje9TRT4w>-NI_<~c1TSt9aG?KhV`!z)LDwY-%^2fh?GKp5ShCXZv{jc&qZxK_9@ zI+pt};w1+%EH5)!Ln3){nD;H*q{AYgpK!-ez%X2oT!28Q?Gt>d>Kwh$ zmf9YguO0i{Jc%*~TeO|RDdB+H&fhmGJw9CUafDG|_eR>0SGs&<;Fii6Zv?WSnn~_tj{vtW8|DmfPvhM4g__4tEi+oR$C#J_ zWs*!B4t`^uzz;!;9;b}m-(ptH+^X6hpCp#E+dz#hv8mV~-N>q-AjpLEH6USU}f(=WWO6aaxJq~008rn~^T*6jWF|ux3 za39P@ZM}wfhQTAIF^r5J0H~z0(xHtmRc)6N<)LAQZ|;tBpQ@gz&rnF6L0$~E_K4%y ziyETM$RuS(MmKtndyh)7J>{*9kXcnoAQR3H%sB%bfDb&AoP$#0-o&XShW)Egn({sD z5t}eQrWAWwoUJ1{LOzYv)n_eTjz0gjK*Z#NB6&ZjA4#?_Z()q ze;iM$+jvt_kj-p>#vv_io>t^z<&m{X2RY=oILE20hbx**YD>~Bqr#?O_g^vVC`LHy z0t{6)b!%c|Id^4ZK>NAm4E-@*K#wK%G$n+XqlMsr#>BI*+zGX1ZI?k*99x=V4vWl~l(&tg@A{)F|d z8(#;W>2+*7c&<*&c-9PHE$wx4y%!myvzsq z_s437a*HXmTA%=Oal0+-^Z{l`fo4MctjPvd)N<$;IoEZ{#BOo3k z-O2YPcg0hUWGTK6DP)i-DmomV571NCTe0)$k35EW-WL}L5XStj0Vjj?0F3vkO_;KA z470+@q$U)C-*j?7$?u9T9z|zWSwqB|81PrrXX(fCtveWHo_lMok4&VK6@u}@3 zB6GAND>p-oejlZM3pm#2yL>&do_`Bk&c1uc8I=$;>;#I^^R+gBKxf)WWB14)V!8+| z{M`y&P9~MDt|2WFq@Ghmjq^pcE;7Xm<%w+d!6PTYAL*Zw!)K0-kopXu}{p zDe~Mg$mh^DMiEhV^8E{SC2Jejo*vWBh%dBx6JfVt#x`=Ghxyu5+D2yc7?4lQ&7Opg zBGNN?t@uo(z=ulIZ4zBx*+Qzy{!nEMF@Q{jf^sp&c*K>ELOJg z-&@~XL?tVTofj+48!XK#V~iI7ecITx)UE87RcnE1d2g#-m$|n@EgOYcW5Tl?q-;Xi zz~`O;;d3;#{xh-|o@%t;g?mdO3$w3uQr zs7n#O-J3A)qP zf9*Eh5*0IpzcP>cW~u{Ya^Nb4ZlQ5oHkXj4^V*0s>uF`nND|=0Ji)ogndWT>#7I}J zcN`D~>MXBMxvd+=6+r zZ5FR_dLCUzPLdc~8zZ-nwX&#EVn4jXG6m%b7%PvI0&49J+W!Duj2}0B7xmOGA-mLL zmi7EfscmH+pF43I1v@!%uaSb;>NAd{WK~^LM~3!fj`rhwK^S^6Vd{{Uxj@@zI+L!Y?+04Z=iJ8k*LQ=Df_ zlcxDL6KH>DwvITA9zig|>_PIvqm>yYj{~Z<0LK>noO)Q!KGIJ6n?GpP3=J{4wpO$+ z6EoVpp|U~wR6aoD3=xrnM_zM{uB#R6In-@@xFPbE&8$%6vz`x{bA>D1k796Ym7h&X zdObETTFEopMAmTFgjm87mitve8yjkt9AQ_C^R#spao{ zF?km4)qx<5xF7+ZoF4v!nr$xQLzp#)&;1n@`@;;m$hQhd#}bDEx$tXV~CIf^+ValBkx z+8I2&XMQsouUg%_A{SHu#RR+i-Z@2SyJ9v7={4#qsQtHw+c;rtlauk@cxr6iy!=6Fs2dBMEVHP(QwXrPe6}^-! zhJP_QD+RXQ;0`j(M_zi4fLAr+%hM*6X>Yznp6+mF4mec+vBxL)ka9Qx_N{2ebE|hE zq??PmA&)&~Mao0TuQ$uU8TTFh(W{c2*$`_1#4@%8jaV23J#hQ)@fX5K;wQivf&(vqF zAQ96JL)=7yLN+U7B=jS%_|ps#x|}qj@{W+|oMf?9ZNjQ6#qi01__#)if@#+{O%r zK-!~j?ws(TpTPUqq`~5AYl}ObO>JSkPqZ^zTu#8PzIN?i)7#Ro*h0nd4Jpt)8Dnly#7hOw_VL|6SR4C0k8@L<+Go?jPPmF=vv;J zXz3QKs77IJ!bW)HjTJ}A(VyVj3Bk!dSn@c^{K*={!WVW%mF4cIsY7!G(p%2uQSV|G zB^*R18F~1HMPt^5W zX>}W7WoSrrlF~5WDg{?1P73rtFdgy5Nh|6~a+1^YFG(<6DoM1_2B#b)q>p5!S@7A* z10ceHz#NPa3Blx4Hdhw+x2|A558Vu3ZsLy3T%Gs-;Ye3WFed7=es@paY$zwIQ*VOZJM!*5zz% z;1h0Wm_|uu$_#)5hAhPJKnIR$Ia-tM_Vg|C$|eXi<{xk4S^c;_IX$m1L96{*V| zU80I|$t|_qmim3w#E<}0osz?m5(pjjd)$scQ<-+*{rlbn9z)(pO@9w?al(kOAdEVVr^A zCm5`GucooQv$wpOWzx|I7gH=t1j-b~n_OW)Ed54L$_N<4RZp;~t=~gROM5BquK~4| zT)Uze#ZrJQ%tq)!~k+H)yJ zo6g;kt-BJxIRS_}Nx(du5Tjg2gHone5HPauT#uS>rvMFrL(T(XD1I#bwWdnA{8-eU{ zF^rN)spf6jqTEu7X_O+cK_R~Tb;ZL@N*8;UWMEq=RXS&50o3v^F_tx9Q#yY5f-!(T zAIt=~B=s2?7(MgbsNhm!lh(yabj15xERq@4W0g?o0uT=hara5aNy!8+L6Rv)ZFLuv zZvM$DsQK32e3;pbFvwlRsUrm7A6^D&q`^4bVz1e5pH1=_(%CMFD9z@!?Vtb)h65Pb zIKdw%=O>(b>>d?sc-dx(AR0yT^3vt7ia=1=KQP7t9P`HNZ4T*~#f0Wnf;r-ZGOC3t z*v4``VUm7a;B(io;>;IT?-STZA(%%S2T5TP6pWk~D&VSyI2*H(jOV6d#^_Ofkp+d~ zT->#_jHLveBwIv$ZVQD~Amh*h$<8Vm?qoW_vea=D&<(o;r_F7ohK)AH;5SZLagq14 zfJD%p?!3xTYLWf5mext$DAZr8M#F}1{s;H*rAx`GQR=NTT@AR27bI`4FqjB15iV4yxbf=R(7 zXYlmkQnzuo-iCa-%ckRVa~uePSW1OZ{G%Ap(;SYcKa4Z#j*TVNq>!Dc2#i~^7{*85 zKZxXZZrK1ePVdN$- z%fTJ0Nfsy5)GpP23Yia6o|LZ3aki|lJ;;jQ-Z8Um*#(F4_N%dZscw?KWL6l&X}PjE z{XhEjlN6PuVQsB~F7PBm{{S&YQV*tZd8A0=+an1#Fa##vHynO_sg|29%RF*uuWby4 z+yKe7i7a<_{A!i0rR30G6;N$%M z6o;uZptyD1rNSSS2{H3>2*|4vsd--j5XYPh4%DtY5Sccii6oT7-+3b#Ck>IFf0w0H zb)6BTUo{Aj^6T^CJY<~bjDL+wW3gGIk~vwJ1t$Tx>yCfIwk=YBG?^s_0|9^-86W}9 zbIJVrQjpTr>NH(WJw2n5%P)Kz7S6S3n4Kh}iMTJ$&Y7fhkf^+N7 zKE!*EGZ#tAQ?iRoL(eaKDHg42lGt6(d#Wm^Q*!=KoTrv{ECxs?8~`|MV{Qgbwcm+6 zFc3Yvt(y#+ZQWZKBX|dpJ;>!xJQ{GCs}E%ULv;CHI@p{36NS_K*&caQP?UySMVJ`l z2L;ARJbZwUmx5RU^Al6IjqNYAtATkP)c$FV%LTfF0w^ljOp}1Y6t@A32FWYIUs7qU zP2MF*ZN7p<;~hRb2+!GNyUG}D5?O}DkE$xOZzOKzxB-bR#y}i*j=WI%zLjHR3~LRY zwm^hF@PK3@sUs=^u^1Q_#{&YoaM(^+sdhQ5*KIWVo_r-N2vs3{2U^q9W!W@pLvJH! zBzDNJM{|X4dY6U#ac!ZbdU?_k6Oosp5s zM4&hPgG`SY4z0A2F97Wzk^Ss_e3{`KrrLY6(e{clS1j6ivKe7}jZ4H50~}<LkkBbm++!fNMi_R^GBK0IOTtu5*lN5i zx(RvW%)P8y4x4an3_-`G8$T1nb8aEjZ>^ytmYrjjnZJcx zg0xy?^1Po-HE-<;S zGe^DA*HZgL`ird1nPn2&12Ef+Dls4dk{c?Fz=Od*F&-XVts2vbr2?K2n++_M%2K*!4c3XPOfPjE z4_fn{ZMn6E&KZO_C+AY`fk+t1Jdiz=YFQEHh6y!GX{UR}by;nsNYYKL*iFa|G86_@ zVh=%)nzt9tWz=5LN<7xpbtyczQ(yh6U$iW8mlq;J?dI({;xU{O%t=*3Ft`D-HnRTi z<}3G)QJUV;NM8_2E(C*tv|&gxDZwWrjHw**MyeJ`BxIfMc4U3AG|7_U>s-?(SXXt; zpp-?sV*=THox^A=yI~v*5!SC;>lWYIcY+TP*xWO1Mi97gqhSF;s`5&)B%TQ$b#?~i@yP*@f$#~g0$z%>jy;wg?vF4a+EXpt?F zL|FkUa(;FBhTgmoG6yZ1)liX>s=AaX@kXDmOE#ZzWfbr$t9eUu+wu-F_yhZ-4qK2& z+H<#ZKM~lg;^xZsOQjBtBdHQBOxunL-p$cig2(QspcP)p^#@TUqbOMXMzprLzqiyp z=w`LuEb{Ey(IN)(2}cX_Ha_Y8EZ`G?U3A*4enJta>T$_#o3RuOg?Qj`!Q|&T$0x1@ zOd~6`bX`QEl&oZ2YmrHC*84R}>#09@!7`x;BaNz1;GeHUxb`lC#Z4`gbKK~3PsrZ8K86$=pXO20(P>ab^G6x-7CUhob|7PnbR7WakUlr(YJId(#WQAA*Bnrk% zu-aLamdFDjXBc8R;-%r|a{a7YFF%a*otTdL;vf~am=*F{-+_b6W1MF-$ats6dXs9F zu;`FOai^H}u$df$Yyp5bFUx?ua7kXGpVjBMqgofUdKaeg6{neZI$nw9atM~-%EaJt z$<9wFgO0;-##+37BC)*F+S68MjxjeT*$GD<-^l@oxX05u$Z^BeNF922(80RZw6hRc5va`9{X=`yEzS0rn^3~HHcO-Cl;NTC(GfU+|c;QMZ?5$-AgGM1pWePI>R!9mjgF zV6UqsR%vKtLE;QP7t3 z(0O^}mK~9p@Ib~0Qb+fJ;PlTteiVWfXu-zeo_nLFXR4(wlhYf+UVG((*_$ zYzH{WJo`~=51QlPx0N6%GQ4XU+Cz{F5%l`^t5$M(YR?H!F|-lL2R*)+rC`)anpw8V zBZO_hV6o@$;~n_+?NeH#-Ntr&r57&ezZ96&ifLrG*A4_(k~dwh2^kpv@cN#fpM_gn zOK`06q$pK@B#?W7*8{olNxKiow;X#NRydCbk~$xM^NO`7CDBL=jB*cOU*%c0I~mi6 zu2MFMCRLDtlB<~W#&f|39lsC8qVpt3!i08h_<3E67`gWcI2=_BuO-Na;KIz(080nz zPaVJ*ABUw~Q*zSpw~{@9gD44>>5Lw~pv@KyiwK4}5hZlzG*qz&QBcRl6|vT&OFM-wB4P}8xI}d=@N;9a`G1QR_UiMy+}Lx zjC9 zjtO(SrU$3F{{SYh$!Vorg<{@I6x^ZEGM`UF&(p81Qbu>~Vp`j2Q+a}Uf_ZMsx;17T zHZ#x>^uejczL#nVj_^ASZ2?#@&m9QB=iZj3Y0%m7Y@F`3ouXnlv~z7`Ex7mKV;wmE z0P9p~;OE0 z-0~{*uZa9faXioVxl&o9F*uqvQi!|)2?S@3N#}#l2Dx22e9g1DRH^LJMWNztWV+eh zt-ZuC;x~D1%P|?jB(VMGIrY!IMJI_a^#-_-J1Y+?NZA55=VFdVPs_+Yna(i8R5)_4 zb(%q`JyC8O?KalJC1_hszmLn=a`w$6Z64FOjp0JD`G#@}GUpj4w=Ek()Gur#it_ds zg$lY`Buf;LpeTizM%EId<`ACiuD+}i3k(}&!$TR+-SP=FM<0aWFS4%omw2qK-HpJFv>zS9h`SpBPHd01LsFLBz; z+q00RjzB!%0fWVU^Rfv0KK4e$pV*?pP#!=}e7_1ie2%$4aN|C_GbBeV>FAH3YO0w{cl-DyPI*rWARjrw?fk43L?u-vkSQDHO zG6Abl%b-UK$ogjExG*@0jBHi1cu-iJf;Se&BcM<-_6;`H=I;~D6&I;}b=?>fBk8fE zi!-{UaESp%$;&oD>DYoZ^5&lph^=F`fkZDV!7|PO zU7ZvdS}S#lO70;;46x4JiXO-h^zF7(j9{2+Tl0Hy7 zoO)F|im`D~nEp1sG{r*7Kvvp#fG`+0O#dzW_y4H#j&smc4?lh^zz%iYT5 z%(53#^MklIe{P*m9ChhbqDzpAD3u$`nozvtuLl70+n@gcT{1~!Gblg1ivZ`$%jeSs zd!Bc7QO7l0qUP=-(HUcMw^FQ|vRObRV2{t!@v63vTq`%)F5IT_a^HEo4?w(dK;!Y| zlT2z$Ta)baw<<`7I0JC&fycl3_NW$HpCz{Apz4rh}_wV|DTEv#l;9QuPMr3|>0ze!d zGq>Kelx?X=-pTA&b(nvulAd!S)u7`qEReQAPNC0Mgoiganu}rDP#v}4jGi5H)E&Q^QB?l`;dqM z1I#gl*fQW`dh$K$Cr3m(n9wo%x#U&BADew{ZQ59nqXu*X z=IQIkIQ=P-g-sF_^QH6doSd_Rj-+Iq41beSNo@jPEC|YryNJPO;PLf6D824h#E(3- z(U8`$vhbNLw19ZebNs5?z)STw}gJI)F_Y!p!Vll}v@f!holS9CYA%{=L}o>iFV99ELt2fvEqOEqXE8&-H2Fx*ZK?uYTmf5|ms^trJC zPqW1&geV{rkC(TxTm9Xe6+k2o zx%M4@m#C`?Yb(a79E-Fr)xqh_JK2ufTxiqghTu9xTRaw003Tj|rB}Z<+#}#Wr z8|Gp>Eo*2abpvwmMnG7uTNq>8p*{OjT0qg-13p5?+4C42bv*I-{zjW=YRTnqL}eHZ zx`C3?cwcba_qu!IsOj7a(raIkW!ub94jI4(lagchZ4|7nt|3`>##%>^wn6RQv_+h^ zA;Q~BEyQExRg9mmPq$yfhhkE1LDqZj-c^YR5vgW5&IcZu{c6p_!8GG8Be#w}Es1@> zv(9tdspIqX65213lG{;CY(hy}J(uqx(E;O#BW?tteV27T!$^anJTHI1t&T5L|z$Rf%4 zc-mXKwh268h7WxD15N?$?xu|%G~P4j1`VHFazO)*4_tQQv`O0LPRl`%#(cqa(_y3s zF|4Ejqo2Ferh8|M=A349ypnA0j0Fq{Yz$|j3@;i6rBHzIe#vr{%>awJAL`BWYAi9I?r|6pM-1duN=F zlpJtRa(eXQtVJNXXv7k$^3bH_?YmjfeqSYvXwHVlr{YUXa;2YJgdG5ov1+Smt!fKGb;U!^R|BbAf`#z@Nb z+8ewpo#GeUO;tVtwf94R0- zeceap@A>^|rOYuwD4Wb!NC)OEupgB2Py4wXj)S!h!%g%oDLwl`-WApV~l>Z%ZLM8Zb1{SKso!p z$MPSgF_|*kvCj61B38nz8DIzLgY8+D7JD3g&9rq1k)P@K^HFO9U5B-_A7_&_8Tw>t>$`ufsRw2HOSLg)830t8}aSvT;$*ygnDZPMx%GZ{Cz#ubW?cqg8`^!!aL z*l899n*=hu0M5<={_(nwJD>Bw=~x<-!<(y!c9RzD6OKK&r5DY(r|zStv)**rBa`>8 z6qSt*ccSssah`G4y+Il!%%$GlBf9{WR9~Btdte{K9AcwnQdiu^xbW#zdyB_k-UA@n zlhZ!g@Aa+SCfiO+18*dGSq=nmAwBw>@sY(l=u(Ll<=a}_nB$co#34f}5;tdo>5hk< z!|9zPcX1p+ofUvkL5cU7KA0Hy>)Yu{#+o#VA%-9~%z_Ve=Lf%TojLyiCheW1_fWi+ zMhLCcZ!bKH8=Njk3dC;CF~)r{Pkjl~b|e?Dn4VT)3}|t>P-4$a{p@; zR#ptkQW3b780AY~XTPsF9qAPqkXvga-d{H6R9F##nH&JE^5sATgW0o<-SOKtN?(f(A2C6e6_%EBU@f%PGVJB)LlC|4#?iq&Xc7CL>!y3Cg3B+3W}E4R2CPB#5G zqCGa)EX-|S9WmuLcP>AR)1QCFsrE9zb2k3o$t{*it)z-J2Y6V^KL>;IHhoXit}3pR zrrByr^B1~9yL-s(ks}^4%Q4Td1JKm9eGMYoO`}Zcimx)~cH^~VQVEQL2|OOWV~#qG z;-a|l^}O~#I4<$F7D6|+e(rDp&q2m`=9?i&^eCpAr^FvVb6hiS+~K0&f(n*%z#}=q zKSRK)QKT;EC8H$viSw9lC1TQ?i~^t*<9F9M;O8}(irDDpYu=GsJ3Ciqit;(cl1f4S z%zy*yxZ^nkZgHHPlUpYC&f4B0U&;;yYr846dBI|Fx1j6m*NSxFqO>NgoUDq%T`EiS zy58PtXT+z?V#I;~1a1fADxi#gd)298^6il#A7?lU3{q|24C8~5(D(keaJ05Fnv`9n zXT@r&H(i0sV#0ND@MhyEQ4noHN?|NysO5M?L)~N?b)(osdp|aIl1XO@ z$_8MUYPWHfBZHiNgRM55wH5hY(G-^U_O`Kk_iU>Z20mnjscvvi4^RsX_5{?I^R==D zb0x&A!5Fiul_!o*1b64#+Lf#}+tZ-yX`R)B2}+m9Q-D|1e(CyT^Y5N&>{m}5F)W#3 z80)uo(}VK#%8ZfIAEh)a>Q`AD60IDu;foA>+pq~IBe%EX+M1GVEF&{W+e;NON=>1D~cBlh==9N$NE0 zTbk^-S4Uu^091kxZp8NEkMI=euGUllk+?44+kyN5>M{9KLJ3|uAP=!*TxEA2PbGRE zaDV+o8l`mtd1Xb4T!L~TFsUA-l6dNVw4M4CthXnIaV&cn7|H>ZKXeh(kFI||d{gDQ z2>}2CTdI;rA6!!t+)uTxShzrO_f#B?I*>hu3w3TljWF4h~Jx2Y$Fdy^Sj|*%PxF6E1)Z1|KmWPKTfW z09{KHsg#1MN#ygIb`S|sk;@&yk#ahd%`}9{r!h0R!Q=zDfAzk! zZ)s(9M-L_f7TumZ52pj$r8TIwA(0D6`<+*Y7-h#JvGnxM6w_|<$YqiDoc!42l1U`{ z_vW2~_8_=jiU|$_e50;O$oArffyj`)@+A3~qJqbsfPumP06x^j*-BexL?sS)XOgEq zI^_KgRa3gl3JS5|vGiaLs@^W1qnP09xL; zu@Y(KV?3=Q{NV1}+c@v-N?Y9u8(EaB)wVOT<90(R=Zx09n?@FN4V|(cKe{j~CR}$7 zN@yP3+e+k#TNwlZG0*g>ma@Tbc`VtILcun%QZjlr*1-dgeg6PTn5LDAd)%~G*?Cey zIDNa3<0=4NLE15bI(I#(U7j~tVpVA+X8qwjp5$Y%e!b3Ww;2cT?t%$ffIvWQ2nQaA z-~5bbr#8uKjkA1lar0wO>rJGJPWDEOQI%q_O1Z{*9_Iw-6(N#PVk9=k$x+Jg9?g&Or({W6?AIv~ za8abV%Q;fgo}Re}r`Dx5mvY7BNa$pVvWzmgn3J8O5gA24Q| z{EScVj1V*If1O)~XbqfDizo~vXreff3PMC2kwsDVs*Bb zQM&qObx;?~gp+_rQb#|<$NNXA#uA5SYUe5nxtjGP>fy)n@C8RLnB44sER)Q=y>PxcaN#jV6SqqXm#{p}zmsqRB;+1=e!6lTzWEZmp=8|6WN+ZJ@2wF-!`(QTuy;pyE% z9Um8mrS{L)Ysi6e2BfM9D*&*0M;L0CiV1XfQ`Ro{LX1E#*zf!Zv(s}(l=}5-TSz5ku)*u zwDdtcrWRR*&!xxgP4cIW>u3KlVb5!%bxN?(u8Khq6yMU+BPr^5zef$7W~u-xuwiT$ zp+RGzIrDmx&ug7B4%owyYIT%7vXRTiR&#+Z?3diRJ8yM{V%=altZ z9fIb0+>eTE0wuW-gF>;4^f31Rylv^hxu=0h17-TO_9&G!w<8O193Rpax^cNpE~zri zFe<8sOV{9(4BE6Agbo;pz{r>NLgHL9y&@=peYz%brddNG=w`8rggc5iU&4A#HAc#W zKUl1U7-P9spZY2nY!+T=$Tp`25x9JH@Udx8AW?L*Qj~Wni1jXFR6Z_&`%kWY5q@ME7> zc@~7?5g9r;889)jd~A6&8q#V6K<^4D&O3CtDgI@!s z*mKmOwbQ=CBnwu*EUvBA{<3lzNP-n-A9`gpzOS*Td?f^|Ua@CpqFn$`byav2rJU z&lMzbrt{W~@%nUi4;8zq-7U)nE9m(8{BWnqmP*eWahGa4tL1Sd;^Cl~yl0JW&#-fN6#oRb+{+2lEev9~vuvUW@^?_u0sQI5^-ciJgUAlcj>5 zH4GTwJtf~tL>?!*)ONNW*EH8j*Us zptP|)Gw$0z{$b`9?-ibXP^IFFPrW(k%X8QtSri#@cCR=Hc<5!)WauM^(rc;@J?szz z`lZ=%IcH@^KH#H#j5CkEg#sztHV+`u;(GW;J%V$CBlGQ~gq5j-dzaC)h zg*!i!7qIjiOjA+3Y55BN_o&)BW#S4aD6G!3famVYt zu97`eS|kB>BNAJGef5sd?xft1Cy?h%Bd}5qm6n6l^^Y0k*K@GE8}iJphVhAhgL{#p z?3uSeOsVs~hxlf+w$_)a#WUwK zzRUctNh@7{ds`k3Ln6agCcdl|J(CB20pW)`}`=k|j-n}#ii zBzV>Rv{=(6hQtD*N$0nI0d_$z)fYt_uTL5Daefw1AaSQr>A$!e1dfFec>z~V&Nks4 zUPG%CFvfmM433QT$1I#r9<%nn5&1>)y)li$@&Sa;n4#y0U|(|jsj+rfR+TRZ&Q2d| zvfNaX8vM%(~+<$KMG}9sO-$4#upddr_w3wunx2&e7KLxy45E z$2-N3>*XB)J%KBT^NHpbapmD}_ed>?ql0V%BTSpt@lT-6`oy;LNA-UilVrbMAsS-P zi(!-i-LdJr+uuHC>m`pAA3zD3{eF|^c=pvDvtd7uz}Ex@&2O)K8F-0ef3{Eo<=)bL zZ#%&Y2B(+HPtfx`QALrDj=$a!O{s+VjE?;z<1@kX6%C*6yQ5bq2hgpBgAF6YGOK43 zh8~m(6F>7C-8D!JCTgVSW`>U(-J>}Zp7S7LmzlTm;*B&U7sSL9Um;yJvz-GA)r&i%}VEJYQS^w$0cxw+Xt3KpygsLq97C7)EuOmub zBpnn#Vj8_t4vH;_i(3T7;iqhFVREudwtw9hXGWz-V;ndXhSUgDo2pZiESSZ{SdP-} z;C_1Q?wYfs8={TX{#MpzD)%~fwe_|57#-5I)fCW~lP?nwzomX6lwaEaol%32(PdT6 zrODS#6>nS3hszuk3J<2O(fg7yDZ?GpZyrq&TMjKPFML1Htr7S-D&O^Ji< zZK+YA_Zf;@*`^~+N`B4!Eb;u2p2_Ac!Tyc-Mm_HAQtc1Z?6h(ym(BAR&(*)b;IEt! z#pDe0DfH{=_Y6|=5cFyu%`bgDdYkm1D`~`qvg&=WH4_#LNJ-p6v1#qipdL206`#En zwZY%f=61T{Me7a%{&{duE4R-H^zs2y0ddGQsvRyI7C1QgRZ==esp;;J&qUkx!QC9U zzGZbHA@md~1`AM=cy+?sSX~2jq~V<}$#+OIsUccm?;k38h|4_$j!c;3G;g~eW+hIj zuK^%5$3t5Vzn`1G%0f&y+XsK!OaE+U+13jMQ0**pS9zyiUkhz;oA4U0d)S~@Rj(TS zd!)!eIs_0cH%J|0?b>2gR5!#O{cB3xfnB>upjC+6U95P{wcEtUS~g-Rk&KJw?r+mD zmK!@b`kf_O`|eW0J;RIV@s}Wev=Z86#Y5GL(>(G(n$UcO(35DDNtpB)a)e*q{Y5U> z*s@rwZES7T4?k$=9^-w26NvZEclfW`r>L|8vq7lssgqjAD8^XG@Qn~m=z%X!d3(0( ztSUcl-Z4Kz%Ic>YM+8z+7{QmpYFm)%7h4EB{4Ryc5&QV+=VaAIh0RBf9bZ^FfH@-$ zKRh19|EZ~rI$4;@zB`w9{cKRf;CqKE&w;8#qWu@3xm1v|9xT!EmcShUbI5K1-Uy6= z|0g8#{+cNcf!jy>Gjn6v)gV1@u0igf+QE4@(WnS7sd%|$gI!CvMir^cvHOXIRG5tnVxh~Jg zyKsR-0TVf6b@>PnYqkDb-bNu|KcVEAyUg?}Jj<#Yh%x>e6`usP@U+y3G0 zRkx5=bH$lbkgO4!T!1;20DCB71 z#c-F+8og`Ba{VVzbtv)Yle>{{}^Yr;!TEhfeF-JxFm19`at;J7g8 zhX_u}=Ra*@S+-C%_FplU)%TEZ4(z_Nei6H9#L-a&`DLpb1`PP%kLBM;w7uLo{ML6X zNZm_m-*$;whDz;56&8KmC@k3JZbeGr(|613 zDXT8^w|*67-8UMbQ8F>ZWlVO{&m0y?H_pck?=Zw%o%|0V)p3K|tVQ2tZ*8T3YJ1%O zKy*r>{Ig>wPwz8I{9=VYC%jT2WAZWs{i6Ul!&E@AH@*opWF20>vxSTe$LcxjZw&{Gxyn*^Tf0X< zO9uzaRQMX6K zkc9|e)hh_^Ig_wv1RuTq_E3!x70{ag^@RvX zC|KzafH%sdXBzvY=)b7BovPoD8dztfqPk|vw$5MvepUA2frZuL?ZGq;7Nv|<+{qTo zec`C&ftQ%N?wOT_0XVbCqzy=eCuy- z*Iy%hxtZ~=9}b_xtpxB8B2=e7 z9s_*-leqjp#bMd+f49(g{CH7q_Mhhq^+EnbA8kMgFG1sVv5sr8Qi1f$!Uz`*C>0`! zJ7&K`>atH1&#&(ARi(a*5V*5*8c#XV#=n$6U;87kH3-@HYZd!dU~s&NaK^36Yb@BT zh79iKYniC-H}+n)ZfjGKdS|;EzIT^dwG&aI@vaxBnY4XvWRee{sa;Ysla`vDrvVkI_S|6=9WxC~@dmHkO~s_jGL zeR-@F&|OIPr|~v9uaN$utcKM1&vuT2;@RGp4Un4{29Ta`JaZOEXovj%+<$?;Z-3#Z z!|VX#640P;7d~fe{Wq|lB**I%t1I{SdsP$PD!PBQIiIJ0ohT@y%4?24RwqI{%R$RLx_-<4w*Qv(!K3oOw@=e^pX& zRIvVvr}n_-==sz>kBh2vF3mQX7u*1o8)5${iT8? zRuNTki4n#j#F%s=0{yq&Pb(&MBS&#n_tBfjrM=CR(;Y`|4E>_%MCdgim5d>&n-z&T zMs=Idx&CKJ3W8frJcBnLT$wKwx7QF}2op3`J=vJZ1vC09B7Ho}5HFT)r7~40o2rsq z_z(YD+P`kC*nsedBU2^QW8(fIaBB%4Q6<%7HVv$knl>N$YE^O$IGlQf zwyy+K{Ni>clv|n`Iu50T4w6MouyP&G20>zzA1T`p6D)qt>0>(j99kogBt; z=$w^Bi~!jyVkWN7@!2zm9&pYRZE)@FD!V6X+Q*5tvm#B+Q(gASN{W~nni}B zsQr1uH7?m-ZX4(0%Ku8pAKyw;+*Up1+_NkRNDYz;2UjYGFPPBp(Sc)wdA7z`5+l$d zGUmwfBAQo$a5+i-*Y4&m%l$pD)2(5=38TB6|{Dxqv{D_ZN2%@1@+tJK2_-J%&uV zvdPySlz9ON514x*adQ7-0&?1Lp*0<-%5CMWexiWF1!_3>|NIc+|3WMWSRi3ZPm?YS z8Jqc2P#{e|AIB%3kQx47zJCs~sX$-{9hGLeEQ<_AgJeGw1)hSyD>yREfhu0X$XfKD zqPLJA=(^zO!LB0`$p8+5tPrb=?I?ldtnlgd+W8(AmkbP=Vg-k#IJF534zKJvhhi{2 zptQ1c^8wP02u$HCo<$3B>a+7>GDT+GPL`Q+cN6R@@9`Qa9qZmQ$R1w3$tuLVLQ9)w z6mvE%_mwI~^_tH01UJHWJoK2-&O80F)S~C*{AjxFZX9eMMWkQ%|7*(dB7^KuiPe5b zMgPoyl4ph747um95byd3xm$j%LxMzv5n=B9rLEKo4^|WPWJ~|;{2P;Pc@lA$9u{?H z_lGSJwV+bBh_buh7DB?|(soz;F8}jL$QdkDH;i7ENW*=jqYLx7${iKTq>K1bLXu>zf5o@+4}}OUT(R$j1=W1B1!Rd{NB=sqd;}<%JS?}&Ps%|oWqWw zCFzKVzipjSk?7)rw>1Q(>qjR9&xLH^lmYHAD!7_gZ?PvvCPR2p1oQZoe~26^4qrka z)5EVutNwG+Gek~EpD-~7(;OIB5>DoUDO1q@6toZ)F8)e zx$$jx+i$#;?g7=Z2`+mGrm8N!;(sw9_WqO9hir~P zk;8YdvV#rlXkH2-(EkIZk!*nY?W!9^V=rq&w+u|>^Ker@o-99k+t4r4E%xMwk11qgG zfnW9HCFh4dZ}cSrlV%svQ{uxre_2E$O~fXFz(?3|zjs`Q`c6|^hYz0>y3)INP-|o& zWfRh1)H{nV$6RpQ?O|ZWy@nBoZ22^)!+t8L^xv?3g27DYC)d%Y?I)g++4ZtJ+1liw zWto_UULdsO1pDpXT>)L4mXD+qe|ywTc|BgM0R5G++s z^vXUbE%r)e{e-=0l~T zy}}|6SdMsx23~yBI=9>^PQ@-7dZpL*QOoCN4$AYP7fH?^l(YM>2BJ~5y5;qy(c<(z zt78K11yo+1TlhYf=wRTjW4A$B{LO{j%lQqVerxc|n9%c)?OyV*+OJSn!efrdRx#n7Zz(WnQ zDd6Y&;;J%&nYuHSSe|Aa`C<+Jo!(`ISH3$uh zF3JTjAMT2QITOUNY!HC|(5RY>0kWY%<{R7G{h5qx6n!!xJ^_Gl10cFfO8Gs!U%yF=OP8HR;C4V63(YEXs+gybAF_Tw7}(=GbrLmIH;=x?gvmJCLXqM$(fUql z1dcU0Y^1SY`{|F|G?*isUk`tR!J5h7_k-4Il6iJV3cSDtyJJlAE)hV@nwQ508SE7- zO*Bk)~nv=kKjGugJUGtZ_Z>CdRaHfJv|8;vX3NW&tn`&97-Of2br@@E>0*F`LmnMK>YIR+*~J5MV3|03b@d z{P559tG#QsJAXXjY>o=cK>KIp*B};8x8Ooq?bkb}gKmlNfYXp^;vZtYK85^xB^Gxc zSZX;d&eiCbe2ZBGN-MwU!ij#>z=@8C3tDHIIZgiH68u)jPiWZHd5ocuI@(yDJo7O^ z;ZZdw&mFVYXzO4A7jz4+u$q}%u$ENMc{;12k>Y85tiM+H7}0at6J z=_gr~s*Elb8OBCpH=x#3*7}czvvFX|X12}|Q%3KP^IMEqb`bcC2TD<^GT(8Vw;(yP z)Rch_(@JiqZ&zA1c6rdh0Un08L0-ybV5!c0sh)QHCEg1Gd8x`pRMP2EF^@Tf9-gz| zzK^hhy}63Nd`p;CqP_oIws7%0@ceu-D+5^(T`j9E1Pn`8&jgevjcAIRrdw3QPso!C z6bq4g<%<}cy1e}wDL+y**26*SdU@poitCGVf+G3vO!-qd4<$mHbJ|H9t1%9A7YaL? zp$xs;6?ga&AzK+&er6BA3atC?vAxZBDgwiKN)Ug1z^npa*2;c5Bc=OX99364U)8Ft zxrmBXj2F_!nr<>68JJX02pa#TI4R08^qK)4wGNVKX^k0pu0E8}#qrxScH$fr%31D^ zfy9H;F`~Jnv-)+(H{Jt$ugn$Bj&`4Mu2%8b&O^SyT$8Byf{A4Uh20Z1uU+MJ4|EMU zc66Pzg(Z_(e zF|}lJTB%WK4~vP?hsrl1a98ijdP?^@ATAN;4$6O`SpmQIMUh?A6J7! zD8OPs+XYG(9E%GgZLCUu51z6~XElt;Xhr$|^}i^+z53e99+J=~2hx9A_m|82902*PKOEg=Tf(YX9;H8o(=tq8c~XX+uf|e~gk_qA{1)S&)++=frA? zLB|e9?KNM^`B)(Bv>^ywM#fCdmNw%H#Jv}5?H%nl-nV7#Qb+ccg5oBDarr)7!R0M) z_CmM=56)hT5JQvr)*!U4R%1MydXAc;M}azKpZb3H!<|zgg_#EOqF4{5L;zcX{+Nl* zxXaYA7s$e6*LuY0)g$qKc77y)c)0h-^D+7q&N&1G?I^X}*;^$kNR~QAI_pvm64mqA zeV#Yx138u|&E|}&^4hRwB4tp)WiiN@iJdv{(WcEbqC;6kGOVp?mlMtme20YdsKyLl z=tWgMnq15E!P0bu%YAt$8S=^(B-!>x2thF=8Y2P74X6IaD#d3yZr+opTV3t1Aj38O z{c_`xPd?|AezZdW^3J;@Gn_Z8R?j%_JK}lMy8HX#*W<7$e{R=L+Zc{l1=O*6^ysR|N%kAH4<``6jFwiJe)9#dOOOiK%YttRaH z0PoVC-FyV<`|DQjE9i-p$g1xN~6yo=ql;rviB9!E2nq& z@mL}s5cT|cw9MnDhos2n;dBV#pEm%by1j*krgVgEYQGXSS2 zpk?_pWSC2z0<~9le$Db>W_mndw$c-Dp!=h3x2r zh0`bY^p-QzjxP22Rp{~Q39@7iMR&xIg24YAW45*ohdQ0WNuohdu$e#QCJQb!OrC4G zJX3i09=1+SiO4bEHhD9SF2aQd+@PQPfL3nNYcwwB_1hKLxJ zl3S=<$})csWTS`|kc+m0@G_lVi9KAc_iL|5t75xy85}nE51cIhoy+XRe*^OIavlAbh8l~?~Xtj(c zyP^;yRvC=xzJ4w1!9)z|`1ZToXL=bR9cI>9U-=T4JCuip{D;i@d)xWeX)F|Bh4nD2 zy)mZ~676>yGOvKTrf2e~-z4*15}v;Cljxk;&D+8@Y$_}$vRFw;mH1N^2dSoQL;g?2 zKGm@&%t<*umS6?8z97iSSdG#2YNr3=2O@ypEdSc(AgX>@sxhatz)|p9U@`CWtekay zxvo#+mFS@6ajm@pYEZWqdE-@GtcxRN+g5g{mcI`nJwW3ah5zBrixsjpjL$md(lr^~ zm?CH3&Lck-q>J?5cY$b~He_rpz{ncE^m%4l^?z|urc<<`qUI36Z`=jvkJnH!EN*Vo zP5dppvg19*)CxTs6bcm_n-t;<8K!aR^_CPlLQ8SAX*f_3=>G>OFA2+_bk>56dZLh) zfe@hYK*=XP%7MN1EA|)!&G2$82w>^SYaN3TyVc4*Z;BV1GRh#!M>L_wq|pDYt!uY; zkh)R%tHRM&QY`s1=Tmf!*1ND+E3ix0(fN4O&97nl(qA;NERl$twD!Il`DSVBq0?SJ zd2ThdN7QeEK(3wDe>aUOE+hkolxamvmnLw_q)T%nXK$H+voPfMi1Up{WoTQThYTuB-O9& z6ETp8a*28xnoTy8``qomOs1vw6LQ~V0PXjCd~kocJz`&x-8kj`V3K&qS2H&(XUy=^ zgc^o_|GoHAflJ4(BNdp}kp< z&jyhqAEwG=OsmUE+7tHf8h7r}6sm*&$(N$jN{}?$+gVe)Y}yU4m8O ziT}%2r>{lIQJiDW-A4K0`xd)d+u+Vo9v3z%hR(9rVvDR8To4R}cyU0!!C04hBUUwM zpF?O6Q;&+4Z513*lAAqzX!CGxQ!3we?uBPagb-HOjj>DjYL=i(m8kJ`@xcF4QMO$~ z%bSvU@zz!_S8t-KOEaSb#pZDC_S)gDPZ48Kp!6Km!a_R`2VIR|gvF|bp{`&@-@nN$ zkzCtQ4%Gh|e$3*yY{GkpG0Osrhx)UC zajzLg9bZLedAQZ$34ry^@4yJpc~TQnoj6(EpYgXj?WSo{e!EnYm5Tj=Bxs|9=^^kAWNC_kea zARNYWDJ%KtpXJR1Pn#aD`&zCjQgj%e-s?ZJSnG(9)!R$2pozbp{FE$1NFe7l)U{|< zaQh3(Z)nKGI0oJyB=t! zbAX(K4j8MfoCxIbmW|X^)61rvNKfvqt=e>LP!_NKQiqO<5=IjiqJ)aCaDZ?hTsP{-K zOAl+8)(KPqUk|55YZxK~Wg)S@_>b1s8Rb7@z;?yAUB4 z;Q^zXM=jtJ-afOD5zW*;44d|-V;G8UxD360P|Bi>b_+F*R{9Z(u4eX4b|3?E_UcJzZz~9pMnyhm~O4LYA-kFqE=Z z+Z)UvK^G~($fel4AQN8M#)3^yeMgN#GeXEU+^2RKVXB!TjmfTP(X=QB>@1jA;{`-r z;ZV+edAhsvJUhQ~v1Yf|?dFAycfR1F?RiWoDK*=Fif{dtEFG#pK5hIVn#)@qZA+Tt zDi85SGY(mTJOD@Q(7|$~XSSTNF-Gn!+y#m@<$ljg58*SRj%MFy*NK%kd!S;B9B;m7 zL#lf!Ke6dMkY#B-$kWSYLqWW0LW#vslk5u=h%d5woqc!2LwLis_O|Q(=hPAX#-#O?99P?R(m$R|EYG5kl>)bp$ z;`oYOQHEKxbD=J%)J{Kq@Wn$*?0MhLxkUvO%3WxGmU?JRD}LTSi|3n( z_Det2ih1LOLZgJ#Gtvt1E(jICOa6(Uk!J8fVWixFP^d#jG-H2glnm0NWVBTA_x3PH zCe2`~REjaN6T!TN!51EnIa$iM`}%Wlz&?O5l;1*1ifDpQVGQ%YL8%*RcfR}QX2@oQ--939$~TXj6O!NZ zO)m#rBcQ+Lx<+%nRi(ebt{>LpcK;r#`)pgqg8*$i$D99E;XUOpFIZ@l65ZU z6I)%$UP>r)38Q2jG_1zJe;`C)TFC_Z zLz@%#d$%Mx+UC6Pu|>YV@+wDS0*yO;MP3Yxb4+|Sj#LBVh!Q)eb>vXdBIw;ahPy=% zzgkX%S~GfyVkkl5W6-mr4+_0P+3ufXAHQl2;Q{(hVwP_V7M-e4+F^55*8{VCMo-dm zqY;hSOtSMD%oJ(|iD3vYKZR!TQM z7O-S3t*qhEkdk_@Tp&4g>1*QDQ}zO;E=Fw>Vwk6sw=9C)A-)8Os+=YB6pZc#VpKfU3i zVm(vkYYJ$ENwtuwLV?QuV&kOd^a%WKM6x~2FBjU*dUy{nFE9W!skZ6bo=o-{+-c0D z^SL-8gAXS+CH4@v+w#UgYz?K?1Cn>*Tv>C9r`SL`{6viXPPwl zC51=OgDR#3)vo*5&tzbO-ueo(5(k_MJpKNti@ASY?I*HdpB2GRJjtzSMWzGof(4c_dh^SaLKnM@(Zn&SjnWt#iA?I{%JT94u9dFlE^PGujmptDyNvsP=o1c0bwJ~ z_y3xGODqvBkQ0Fx>406WsqB5|)R0kMe&%>t^A6s4TbxYQzsgYcgNNz^e#$Ny!( z3l5|^o0FTkC2>NSXd$Y_0QFB42VaG^H*1c3FOQz3=%99xdui{cY}A;+enU=5L z$m_9|?12J3bO|^7QOF=Jc1?w}4P(4^_HKGn2j)hu2q{5~cT+Fw$yFQoHr#6I#uK=Z zJWcp37J$=$5W0(?aeiDVg?A8hS@@l$qwJ%A0v?kD^7f}2Q0B7}{>Vk}gV(%p!`d;a z*?tpK@$|}1NH2?nx>o_lw?48@QC&S$OK#(a`zm$xKcY^6Q0Yr4EJKOSAt&XBPo#7q zB)t&Z=5GbYfh%ah-ruzS=QFo-WWJ2Hx4s$eaNJ)14^a6%+L%gh9}7`!8#vXhdY2P? z^W5fbaC4$U1E69^s1^`SEqlZt@hl!{`MOM53|WO6zyH@lnO^wODj%l45-F=m61e*N zJ7Xk$uzGturq8eMhNae+hO!zX5KnF^S7?nL(T`<;XNNl){OvJk2M`OW@wlK05pYAI-Dk?bQ2kI6b(;w~mU7P6wFYX%L@^vTbPff$|wI`haS{sG#>! zpP>n(6AV9Ii?$nRnLNr^_MjJQW<#<6#r!q-&!k5Kp`sk`k`OTmk9|OQlOW@&J0lmt3-j-b9`rUZ_c~no1rw&LP15=Fyzbo{xZSQS5zr z4u^uLisp3}O$gO1V@a1Ai8OH3j%xjmE|C2fj4B-tii?Zcv1V;jsPOTWiAM@Y zJd8KsMM!q|XUmn%-}2cl%KHLL^N|SoA0Wta;m4{T>dc4Jx1xil7LnH`1%c4kQnm#7 z#X;}8Che^lls_d4Am4|#p#V|+@$cQyZ9|nE%giA`ITPFFzQLSJT>2yyydIKGm95j? zwm8DOto_~VRiAY{|LtJu!ncHHJ3gQx7u2ej8hgu%XNaSlmMDeTukdt6{n` ze)DbY-#`%1^{$WE3|G>ZLkvFBl9$58F9DM874y2ZCWNog|F(Qo_eao;acIS_XXBDM z)7a1GBbXcme?0U0T_2eFMG+}3{Kg;f{zQn_ z%^!;X+w1KBZz}KwJ$3C%nG_OmX1T3Nv84F@(7XtBr!}{{ZL{j%iVHz?-~CA;MePl< z<+ZLDnhInL`4}#AE&%s*cqIvJDnY#hM2gEjq7Kh%j{xfgK?%ebfP38_$sMoCwaAWKM_Hp&kHPGr}UrWoU0Axymu^@s(#(FxntTuHz$%YNl|Ll9q@_OdiRQP2E2v3 z8wc*Zy9#K}tO~xRMjPzzIM)-0+AFR?$$qvF*pyZMyLT@d^=PO38aOIsIEN7s%IywP z^wxk#ib$5O4B)-!mDFL9Pyfm%6&a7=;n|{Syp+-oWf`4q5`7lO{ zYp8$haQ3>6duX+P7T_D?azw5U*`=)b71KGZ7oCQ&Nnkcjdg?-frT&R1;fIgxX9)p( z4;{Krl77sIU|jCx{G8yjVK=+-$IpILtY37v*KU-)%`zb?f#PxJl`??>5Q0gB=U8j` zAYHW)jA0nx>xDA;h1W7nAi++h%kPuT>V=I5hR?hr$4~Z6{P|p+<+-UL&|WZJE@N!m zyxwTyz|&7)dVYL>F^|hll?8i8UTz6>?OI%{9%REJURmTcvN|!$n$PkbLWLzXqepCN zAFtv#{6MR?;~wK%hHGB7Y>{66 zM{DInAm!Ub<6qf~%EvGI4=-X1K2@twz9m+1 z?;&#A>9U^LmQ&_^{`PO#_w5{NiAM2)H8=da8!X?ht~N6Om;rC(Bh>!~5IURGraYY!P1;z13c15v z_P2~#)r>Z)AFAfI$F$cU-nQ>4OUtt3-+sTJI!C?mw(jGy^ax8@U}{=-y*%m_HWaH$ z)pIFPbQYQ-M^4?TtFOI})q^ZD0`DS@=@%#l;SrXD*JDEV(EEISJ%FmQMP4!fFd}BA z%5v9nSjRCTu7Q>{TO!hj-YEaMmzl0R#yR1ZFByoq*dG6Xn{_^_lue!j zd)fau|wY&vachjMY)dWRrL4Y{o&kRF1b7V^UrVbeH$Jd2zG?A2(NoV*o zJnX{I4_{PcH~y0B^(*~#yh~TkhX8G*78;i@Sh&|Sk}CW1Zb$YYgin*j8kSm!m)8gQ#{hn$jr`LY6F|_&T0$7~=y5oA z@G-frUmSes+7=YcKuXZ%FiH3peanv*gpW9*j}~{}63g-?cQL~}Z!8*$?^qVJq~O4(+PZ zezQZL#Sa~0t(vw(U1E^EKL0x8drvy;&)U{+bzth*76^&M1jyDT`Eb~0cZgXi;Mr=z z)aaf0!My`Gg~{Qh^L4w+5Dxy~(p z$tsHwOQ(P71+sHO)h6bCgXC(J@%n}`i`MjS{Mzjri)szA^k5E^KW)Xgyj9Q!R|j?+ z4zl(ynub|gy@{$eKEftYpunqjkLLLlzMS72OZ54xn3Gb8S(;jhi#WDc7|Z#}ig!uv zgt@g(-`2}d!AcEAphs`gq?lw^ZIDr~@ofULYf#0@-BDljfM+A!A(|}jONV&Jcs630 zVg~P7gDfoVe~1NiNUmUnP;Yb&BOz-4%=o`{jS_XPZwviuxjjxy1hTb}{3oevN4Y$n z5AZ-$MMe(o7iOsi#ba6|l<%4)nrgv_=uR}!1F_s}9zLYKvhB#R0z@*X8c)!LqM<~^ z?^3Drk~v&=-vwJ*g@sp>VRB~?2Wo%RLdMc`yW!ryo2TXYu48;-V*h9X!W9 zf<88iZZ7bu7<%zE7*EKB(`G^*^?+jt3>2l%AI`D`v&kP#*$J_k-2qeK@c1tjp)RLr z)y+J0vYA*zT{#|dy))7!&JVJDfTol6r8Dtcxc%YQ!Q7&Do4e*0VJE3_zeOrEv2+J5 zXJNd$zXq3M`{hbQD=}*!1_6_!?{O2dmTD@Cc^sIZNkQQRk~rfCA$xC2?vXGHRFH#Yo?w%a9x^qG zEt)9!1osP^u~}UFy{&yZH~OD7R$q*LH)~1}Rl{f7h9>X*qVC$8Ad-y_r7M zSodU3wE0ut4>bHNIDJukfhzBA5L4`!M%kNrQsRmKXVR+|NsHXS`+-#h&x*0N{u5 z+U(I`r2{c6;$776t&C_$Z->N(%?A$eE4IfXn{>tpWVISCh2$vm{m(E-Z>qX)?nctJUyY8gCu z{qJjbq9rZ2sHOR;R-kv*0BnE0H9dGks^#Isf-#dEz&JvWLeL?O&J%6E*KdHMwO;so zKa7cMlFqh^5Uw7IGuk%%F@Ao%XzJnPdECWxzVr+AY_H~E2s&x%WU1GQi-a1OC=s(} zuKl>PusZ?`8-i_LPVaiVWSDF`gB+JU`Fy*(C7E$SC6R(12Exfxk1JhW+)QD7D5;x+ zl6KIfi^uD1M!^ab)-$0h2dC2I`uhp%jy+Rw>}aD;-zqpH^4HK+hpuIGqja;mm%6j^ zvLQqC>d8R(8KmgJ%_*bGJM_K%t%&99;ZZwV0+YJ2EB03cd_A1M^7eKnG$ze)f9_%1 z@h2BBCpI22bZ||pYNOT|m6K2Yy7o;<;|0$8xMr}9@5>!aMZ``I*z46YTV(ZmQ<`;a zzIs|t07g%-iYi^$-k-&V?tffZwhxyrXKs5PwDJg)?mF%SnuI}vAyp^TNAWseu#brh zT4R1g$#qy}5-0!wY2UFa^z9c3#kbx$FBHu-J--7&ml;EUn9Enh!C3lEC0X_-y+!EAoB zLsfH0EbGu`{f-RoM&=}uoZqm#_OFv$Q@}#yWEy>zyF-U`Y5n&caAyGv2^z3g{2~?S-|Y zsC~x8G!sDaqll%xPr9}36%+z`q-dn!?Ymv(IGZ=T@0DeX5bgR1Kzd8Kcf>0G2W<3}*$)?mdEI7$jWBKcyzjS`)bKLOJc)mi5ztRv? z;5pxr(p=Lj%G|;YQTexu-s{D~rIBXOZmyAb33|VF^zt2uM9YTHf9U-{3tatK+WOi6r$0ryX+R(PYigY^O}+LeOlfy@eJF zZX$c*$I5pJRy84}Q_8nz&Pz7&nT^WLHzukMv)=6`%)tepk<1nE?n47sahuOY+nc-F zGRYZhVy^qoQ5PQGZpPnq2iWF!lWbiqJL5bBn0!-IlQtU1xH?zst>y^>&{gJ^iXf&Q zA33Y4ARf!ofe>%zapoRWt_E#lX#Ws3-a;~Om23OKy^&i(Qg zHQ%F(Z&Cc5d7hEOw4lqAp(~PUBZwP-LBLACIwn$}o2}5@=T945=aE$@t zKIP@x$ucmle2LWRxkMJtW52=5Y{ZmIA_SANVMG|+I`=1k^PkRzUjnSVZqEdSX2Z6k zi>{8-GCTGZ>?uCgbv%59gD>sw)&%#r7Yt@F>U@X!4hw|i!$~6l@L!aFiXXm&qCSc0 zJ}b=oqVp&)b@x9i(#l%3if69pdD|s=rk4Amm?3dCkqS(F{4?uh>xIt!+($42PtV4S z2fWlU-&4)w$e~c%(r@dCA#JuwS2Z6B!TS98!P+w{M?3UokBh8))b<5iWSu~{q^F}; zx6_qGAC6!-EEXOA9*_5uAke$LxTb{rlAnV>Lv9ZlOfJnQR>^(WK&hP)b!U+#i+5W) zHm6_?w2$&9hAhq?%F!H4;LoEOH)Yq_RO7E!`d-Lu8dHGSPu$A`Qn?;D73dotD2|Q6 zGKmBO9mL&L5Hq|HQpdsiw)@LHW(?xOWrJ3O?V9#z`hmei>QXnu9(~BgQhX$hh5jk@ z37z$^&?WD?M3+(Tk`?Ge@7R?>YR**fXV$Kj%g`^Oz%1#~a(7`l%qG|!(1-s!rFmo& za2xErk{7(%&H0QDp3WAb-V`LL#+eAz@N0adau|iA%W-u+yxIA8kC>Iap5jjZOsFL3 ze*U`w56U}ZNA>QaCK!G@f=e@-M5WoKjimeV95BEs?5s`_^lvnEZRJ&mx3wk56*NVJ zEvbg`N}?XQ5-bI4J;=9T)aNQJ@xeA9Gm?fCwd~-~>}$eV{FQe@AGzlYg#IpVZ1E`^ zN1L*8@#Zii@nJ-_GUY>09@iHAy;zq_da)pLf04)D$8Po`ZYho-Wg^GgvcH}j{gNdG zg@ULN{n!R?5U6i4L@9?*2>;^`hpY2#S641IETwDIZEMyq+N2$uBivxL!2EvFrczFl zn5$H4>Xyz&8llzlX6^fHmhkO{dmr4tQNJk^l-eJlG2baut!JN}O#@{yd+s^19YzQjtS@>4FL=}IDh!w4cXyzz) z>(q~hT(Dv(Q$&VFU0%^;!-4p9_k*+kD*lly zl>u+=jjP?*gxr1!iNuiTGmX&crW7NY$~w;~Ucr*F8&TImyxCa_|NILAmjNvC3~??5 z3rKV5eJh2r_dbdy!~M1mK|lD<0-v@ZiS_1ZjSN@!D}E5D0?*j$U|U}}1h~1i93Kt- zMFK(Ul#7c6vsa9p`vwIYT@D0@0;s-(VIV(|>|}CvZ!#mytNG3@RbYz#Ks8G&f*^dg z7$e${MH<*L8$&d6C%`c%A7Fe@@tnl%lIt<;G@Aieqaac|V{B z?U);~pxjjHSL|+Om4?x4HL7`{{$dyz!@>s6SJFIQ5JGS0>=}F#b#;CsUmU|q15W0n zx3^T>ZIMJ((Yb?JG03bsqm5^8Czjxx*!Kv=Z!mYIL|u8_78_ z`s~!ej*0@T|J)s}G!{+27Zl&_!FeI(Nd$@`$n`7xLny`2JK1z9q{{8~t50y_2Lp28 z#5>kX$M5#sk=tiPI?H0y#GoHuc8Y%{mkd|+y`I*hxuOJrqn1NZhN_QgG0CgUGfvlS!$T@W#|joT6E*NW)U?^zBq~5;@pGO^`N1=}L?G?> zGqv|-K8BXuGY?}}LovB5fe0CJE=2nC75oukb)51hF@XY#v`}0NMCcVE7kqTMezw_|SXu>Np|$J2 z&BG{=+>fUPVv6tK0QN3jqP7C1hSDmd{=fwv9&`Q1nn8(oM@NF#&hV&(8~%W{QY|Ch zWj!X{E?;5Nl=bB+(BZD-*Vos)2iy6I((N>V!x_PqhI```hCS zNAlduz%O#cIc>=udT&zgL*to30H>fKU;ubfB?)=`K%EyGPnq)D)m7<~ZV7zn6&{WT znEHL7#Q`MRq&_^zc$kvz?70*^+#MbSBWV?qFHb(n?AX0EpK3AX2uB;w!|v5()9lax z7N!EavYQ3MB|<&;418%iH%yOdM9A%CzrQ%`LHpl3kui53tOgsZsw?I}WA}_^wqQh_ zXFdB>Mw`_BT77nEug64leGS!34q1`RV1Q&ask5MT(QaB4bG$F=4+AgcD z#XYOBX_T6Q16&y!;^(8q=xmcTIFL3}@Lf?yUA4>GN>j(NDs|+*l~)QhJbsRL-FQd1ImnmYT?!^StmHJ?!{}Lm*iXz8McbV4-&=FWKYtppwGZ(hj!t9-m>RAZu$*ZUVh{eiorao`VgJ5NUFHX;R7uPU%ILY+JM}wNC~*HC zl<=dz5+_ORgrTy0QmBLV_C3CLfE!Xl0}v*avLE7 zM_<~`*NdTeN1N_NA;}=utAm7s{=>tsuS$N(h#r5@Vd=z#ZWFx-AO3a8iaqmpi{w%~ zmX|y3B;{5u3mGDY;{MkBTtb|K@)*%xf1Op9<6|i3KZEX}!@nv8cpkmAs#`b^%@e<= zk+(dGCQacvaq=N>a#q>t{5!$>`>*tBKN(D`QOBepx8BJpDBFLx@6k99wq2&Qj=RNm zbCv%{{Q|*7Q4%cxtkj^MOZqo)zMqJo^Yq->#0Vgqe^F;dFcq%ZI9-*;P;sy8<$T?O z4@Sz{x~G|gh6H?RL*1v{3V0avd?P`2y=b9SuFZ~UX$PB?q)lmO>~_MiV?$oBO*1F`IxYY2VOfW>%coM za6P5W9}9;3URAT)n3AW`RS`k-lV#xJkw|2FXO?-%j{4Zw45JgA2lS!9MJb1EWeAf27^jx4 z5I@Kk>5m;-e%Pr%kKzG00P+*xzUC`3+6~iN9F&Q$UQv%DCjuuD zM#vcz9bJ4SIJl>dvjm`avKM76@yO;6b+LpW2FxIJbk2%u*`c)_OS7V64X*ZE*t?QJ z(9lU#%ydw>Ef>l`Fbe9Ra(=|*tV)oDUF~TO8V-h$#t7vR+xDU@3Sf;FE{Zh1UY!wb zOkVNqfRlt7EW)%%RX^9&$5-z}FR-$tXMk(X=?bi0CZRJ+^VcaXFj03Oe)Ia%7IPAn zRhr7U(Gb4MdZ|k1?S(0<4UbX0?ecn#bn9g(#aW+=p5^z)2uUA0B@e@CEnQ5U8mo<8 zZg+ox2-Y!8X7h-M1CTs$K#hMIGrZYroI;Nj2#zlm3E}7joX(w`5Vx!4G9pWf;M2Y6)$?_fk|A0Acfi|ChIB}$9&4~TY2xKtASZ!c_@zk~Ka2lb zrbqf1pstruF57S#2*%O$3(HfARDrJsf+Wrpd|JZN9p3;hY-y#Rgcw1Br!8WSqOXk< za6hi_c5F&TKt=pOkhu;rH0Gp;pLbIyJ6E7Ab1Z241=tTN1OpAlsd>-fTl#4N%Yx^# z;OfXp8VSf7HvE)+U9VP2gn}ke>9Y#+ zm%nLfoTD%HQ(@3l1~y27j4kp=B{d?sKdJ|C;^rqn=Yh)G85qXJz zyc1P}dhT3Wp<4reZt~KG?ScC+Xgm zqx)7RnHhDvx1}x=)Ov!@_I-||)?18_*1J$uM$lSDxUaaXMay4fLBQ|#3%n)2ArHm7 z8|yfWVAx=l5lJ_*02Kma7{dQ2E1JcE>I*ggx(5g>kJ>>+vsG0IJ6D9N`ZYAQGzD^` zCBI^=@H*Vne!**(x%gcYXOwwP&(sds(3OCnYQLy3$WQ4nKZ6_xJ8z`Vme1G6iwcCd zf7{I=9oJPr^*+c>n^Xl7{J>tA`7MQ)Q)jg%OBvP{-p~lk(!6dd{yXDok%I(<#aT7%~k(P zqCI=NT~E>9;AheMiB>cL@{dy2(Sw1%EHq|GD-L%)YD|td^0qX}KW=v~RmfMs@9%bp z>?y)WD7^zZ<3Hc${fXD}VLK=)J4=v}T!?c zK+PyJY?!1;>@}_E8m)k?h<|L*`xKr;K%%izl%vXbgLehqxcaQSp!OT>8YkJ9VAM9w zrH$SY`#~NwTfr)+TcD9prM+&`cuP;cDlx|NnRgzYOZcC|md5?5q-}GILztt%L-5n6 zkl%^at`<&-C`dB@RKN#G|HxFrJpuGKbAa)KgyjL=$4~znz8HV7SBYReUPnhIg)8-~ zOd6A}W<3E~>2RDy?FGhv{x>i`d>C z?l1FSZN1Y=-h0VN+1uZDb!STs8xd$73@-L}@;gE$VgC zM|qtQ5$u8?y9Uzy67)grn%zvJ_+Y7e6pz7Shp1Op0s1J4hysY%J?|6@cJdq=$JrI6 zJSmvBcnyJX6Tjwuc+qKPu_jW4m}0smn%n#`&uTzw7iZ*(rwB$z&l3DQ9sK2If zhHR&iM?C)nuxniU2{wLjtJ@l1dv_*`L{i?~0@>T&Yocz&9#zA)ILdisPiXNz3G96U1C*NA@Yxu4yDlqm~_+*&Dm{4U1b+6 zb@698$C{mZs-g>wCKR0wfVx*h|IE{71dIpVRX>wn;4^LH*1tNS$lW;v2xR3l^?D;k z5$OE&Mt&J_yGIH%Xzj6aUt(-2b)5nO!4CV+G-lA&&U7!8I>%j|3&Q5O0}PfesJpz_ zzsn7PGun1db%#6tVYSZRS6Y`9#-PXp^wPD=+@RkBnhJP=(bP%0f2IH9=`NNqg7*W7 zcHBIk&YS*PysoqZoC4tduHlyvS6bH8BJVLQtD4w%gHZ&}_UZ^h4E$BQ+qno_QqhqBY8yj>SZH2OHwDq&|qEQR2>@8qN~k|xf>Ju|0~4I5HOjGlR7 zfb9?PD6H@e&%Z7ElzJzJ&?c@JMzbZoS4!*QhA1N^K6q}>Hsn0IAn(Mb(O!uoY20d1 z@&YN=d_?JL;EtIPC@}+@ zbPp`mH{LJwL6;O5uJ-OSR%t@^8J3jT38#-g-HYN)YiVx5y!08bzf$aSw_2HdNgW}y zQh$SOO{v9CTh$V9w(al=8ef!3+q&T@!H`U>jK=g(S_3>9GK2mV^;h^TeB@81 z#VpX)o39dDxkqld^qW0Y*6}dOeiu3dgHao~tf!UGJ>R8<^bms>pm+gW9MFvYexHq) z9Mp_@6-5r<_B*wLs|cZ46?zDlp@uhZ zKa@-BFyIwb^m^z%Vj2q{P(t7mPFJDq6LPl3cXJQ zeY%%C*!iJ@AYXkWZ}B$9$yDW|JnX{ACLt9@Th*&CzGuZG(vhk1u`zpFtobpdhL!(h z7zEeON1>vPLXAI4Ph=CT$2og03XAj%Jj!8aXttH;^|hAU7VU2SAGt$5_@;D5L4ae_ z?ARPF;eP;J`DoZ}d;q137{c6|S$fY|HT}g1N;|<=JcX zGKsR#?6}|dZHN_+c|>NWDa-G%xF0L$?Oqcu`QIk}KYjYtfGzLKRe-r^{i-sbb)C4U z$%;E437^*NK|usx-B_p5A?o&RAAW0*tDIR}-?>(52gqy`qhJYt)5f2;y06+q=N6ky z&E${%C^}$i`|Y4^i)ejDfrA-Vv5D{zGT2VNy7FY*lm+2y=w^&&&=4+8_?31)F!XCf zg+pOS@8>#Wap>iBl{xO=ii5(0U>$jBX-YlIca5LK>6UTSh#EqGt3Qcj)MZ@oguo8b zpq$ouw#-gG<(-;sGL6$GJyHbmDJ6P;9A@knaDgu?%|5+WNpA$L=(p`of#zJX^BY1Y z&1i&xQ`Ubj)oBeL6xus^bmLrtnz`7V&Gwe@ez0Ieo%8i*WGNRvnuAN8Or0AqJzHgE zO>`tvWx&zg9qVuHJ8~?qqxD9WsTh5Q**7&8jtzn6m*b(NI<1X&ZHA@BLc4P9;qq#U zD-1t6ga$bY=2Ba&eD&s)JfA<&ag2qj%T@|DjyC2hR_|iuqiDaQDeyX(m}Do}KriYa#36DpbjZ~tuZu-2KY)JnqziM0`;y@RnsVe-j*c^MYIC2XgHSK^qaCsERE23URjlAnppI7l2N8@>L- zNqK|*W{P2X*_QueZdbbC^#{)re-afl=O<|(-vy)XD*RH!(t@{XS%ZX?!ic(uf4V-$ zJQ&F}TW>MK{&_Y#CZk&P`A6u8wgN5M{TMm(^KIm*OmR|c7%){-GGCbmXRh^gT^@9Q zhTz{i-pV-fX+?m=2T9-Pw0~~)PB&CB54N5tuQVyIb`D>kjV{?!X)p|C%y+y}Zmk?R zc>G<(0&p^iiRaYYlXx?w=j=$z{V55E{gjcD$iA|KXFZu@_f+aIw0I$Vv?p@yc%CTa zdJDyiSu?i6XZ;YuYn3*`2uQ%W2sl(40zB*5J}cKjOp!M^lajwr@^(-|!;Z&VMNH&Q_#Z1$JaCu@+!<*6ws#ZFJaV?^ujSuwt?J)1l9#8`bw+ zyY^ETby=SkyD70W`0So=C^Ayu`y=p4?k?wR4EWahnbf0iPF4B*Qscp&T;O0XrhX7n zKU-n;jE~F7JsT@&=R;@2MG;n5RkdVOn!$$_ar59TbQ^}f@-QoPoGbjpBH&YbZCI3c(&Scb1j2D{xoq&ul2C22qI4>m8x4ht%;Yu)Ob~zw)JnBLWQ!6#! z$yKB4u6KERWNj}HB7N2?x>^(hIjPm46T1T;T9jCEbD9RpvO)SA{KcwFrTo?BX(Ev zQQSmJgrY~vjvh~-{Y8BLqA~Cl3|FJ51PNuo}wy21+o}9Zv=_-e(`Uy7a zS62ej9Djtk;gE=^VoqQV(x&?MP@S}GQ5rD-86PjEN7b@E1?&W)aP{!jGYv`F3I#}B zp3{6I7)f1MOMFUdwUWX_+Al*4*w!Vg>`AT(`-H^wE1*vQE7k53+1(h;I5;>iYBOHZ zS8PK-m1UEjS^r8%Br!HzIdLV&4D;igq!;Mw^(O6CWAq!Lgw-(eZIwpE`*OWMJ$q=DAMeoO+N7Gi)*}qNYJ)kMy;!Os3{li4Kbmil5ERs4gtS1RO z$p37QmvB4YMNUtse4%B&U&8hElsjqT@Al-@+otu#l>Ma}m#vL%vaq3b4<{^xJjk8a zN5dm8iv=0C5iRLoUQn==j&u7G;pW$cbAyPn(NFJJ_#yZcrTV(8 zHwJ=Bm*$uHl^+GxHY>$8HRoxtA9D8%MQ6i=GY7kotvCnHYIOrY$ei~5xl4gSmBTkO z@BKo>3|L@v-POLZtO%B~fn%AjNUcZAogL+a^RL*w0+p%LUz{OD>6rjw$vF4R)_ znP1_~hzPo!F00ClibJYADVL4QW5O0m{NbB#37`M{vQ_?6CnZAzU) zZPQIP_2#>LlRum{&qr75?Q=*p#-LR)PV;|&3Rf=i{8>MOA#zUw9{o@_P%Y#*iBa?e%F;y2JoYb-`a#bJx|NKu_tJfS~|3T*sV^*eCpj(Rey zcdlx|xI?f4`UtU3t_Pz~cl?=TxUFyg@*HrVqaE$UTut^1eBKd4)31G#=_>w8fWO>z z(+C_7#u%dGRQ4>h=Tssbuu8?8Wghr-mS}`r{ET}y|BRdMJ*tqJV2<{KoQ{aKYmJV$ zmolQKd4TS5qh*>vk4g2T7*vRp9H({A!RU*wT$NfJNeaXBNUo&p|6^?KG3-Pj=#$n1 zG8Vj;EU63SV1$6V32KxNk7`&6X_{KKKktvx{fDyy87W>7hQk22DuI-oN!`kTk5LO7 zn2kcV^$c;CO{(4K`udMFv!X|)j{XvHck54Mx^JgaCZMmFGnw<486l|vG9mJkP5R59 z?DWz3=aDOuSBBrQdLW?OvqwPvKW>95p7tBQxr^Ds!rprFJ{gMRz_G-Ip^&J~#y)~cQ?3&Fhj;#26ipY_X$jji=B3)lozMAV|hpi=}Dv0CB4XQo?jAB3%Ep%<` zD_UQe3U9S@E)HpViYO$nbB@vM&S*|5STAMi{a_rc!9NQ8A*1!8N~&l+9#k%@4Nmo% zT}2LS8o}#!rRd%~;xc-_<~aGUlO@qK8^yjd(*KY9(BWDbD!_1ZPF6PpoPE4r#+N0N zIDn$^3^mTlRf3UK)h+vhKaFG?;AZ*H|1)xJ9}i(b5;|$W)3=B|LcWN&#$o0-96aZ) z<6$EZ_aolcn}(`CeuCIcJsJ(nKw4yJk%P+FgRuO!854t-O54BI#V*s1uP$Utf?&j2fX z6zLR`n5lGmk*xEmy#UZ{@!z)xN70k<@tDju#g&9$7ZDDcKa))Ksds`xs=f2t#+MHz zSNWLY%|o-JBrq46FcpJ_$%aGPQQ;oDJF6uhq({Z8Gn|b6Y8hFN}=F+)S#`*u9qh5Klx^pKi@k}>!&!?JJS^h)S zghI@Q>|-%LE*RN$w=yZV9!Csn(b9Ki4KgQL$7it1FRr^Bd z4^_{~Q+V!~)t~{zbU3bgnp{yJ{wI-{35rCV3&IbAHV9wux47q@C^~2=y1N zKrC)s3T~*b!!Hu@#5D)=(&PzU{>hT7%ZBTX^C6wx5vw_o%b1gI$qpHpMB_W{h%!^} zT&|{){!Z;knmlWEFY4g-wn_Vy9d234O-1I5@*pr6$FzFH{+e6_xBn5I^3bl+cvx?m z7Y2k253;{U>`1qt>otMCpl>Ce429Sb)?w>Bgn z7ab19+KdP&rwo7_CcjJqF3-*mFQ&4KyE`aQBZ%4f>)gvHH%hT+rmju*9e_ZI&Z*E! zyf6i;mDuR^-8xZkf>w5ne?XebqGPL?dP((zn%!in<+ZaJ(Ri9wRi{o1|6KiO`3=v7 zF76W}kPv@)M!j}y>OPlQPA?B1s@O8E=+Mq&!zoMP>lSwzaBZnYOP~B7km*F+%q`&j zyO=8Me_vxw_B)K2^@Uc|Vh5i}yDqxgcXOHALVI$|OgL%_A(*J$mnVy9CDuC5+Ibyp zF4`6PvUCj>!N=0IuU2~MDM@!z$X!`+AM!_+Pxq(ORuZH?jv9yCu`zt1_TUtwChxj< z2n0KZBPWrwUKqlx0xl@S)YG*KyrFlK_nAnxi*^-!LZ80Jaz$h7ZO#}@SORuV$v`Nl zA?ph(F3uH0=Ix~QpY~$2rtvB6ccM0EXS*Mh=dvafxQ(|O-`t$jj0gE{5AT21PMc1L z+~Q|sQGiPuPHD4+4r!`SHw+JA99URXCIvu!e{_hawf7%f`5$0!lgZh0(2qV>)dTxA zSCB<0EqQvaa{MFMQ(J z(92-tFKVFj%)X)rb2C4P7!3;|HUfL^t13>7A_L4f77p5$*J9q(2>6a0GdJP!^-%2g z?5|7vlFa=E%W0RtwPwM(N@O4b0Db~1|H0S7{$Qq=Z**(yZL^WLWjuhhOC}7oejG^> z5eh*%SPw1orTSyhFO^?D2$af95Di(Sz=4`q)?TQk2Nwr9;z+jb9X%PKoA#^OCM~F7 zPhI-L2o=o+_D=Bi64RdFD}~%+o?hx9G_EM*mO8mn(7)zh6c*Oet0JH{c+;nNlKKaA z78b?>MV<(%pAe%H$@UqXiejVQOFh!7`xLbBEA1>nMO~?COo^&yG;di?=Z7zHUm?tn zfY^Dct*eSshu|Qd@){!8^)(v@3tsTD*WpPV577mt{$3a+*UL(jU8Q|cThT6 zKgFR6uY1WnZn`uU=Gun+wZ9MnazW>dQ7^EmR<}QlkHSYh`$Cr>Fpy?^)fP)blgS7F zE1%0(aBtn@ljSGnX++q269N6fgGh;mugFfzi{aNy=mTl0qq?1c^>;#6oy;=-yU}B_ zX;YEp%6qIBL89IOG(Lj1pVUU9yyc_%#MigTX5%8Ef$rJ&PdVtU?F16)Fo>Zn3?+s8 z=3^Tp6BS!Eh7bYzlXMh;`de}4(KDi{%NCG@=Uy^K@pRJNSwBOUvx=!*Iq+#^p$fbAln0P#zt^9N(kADqj`?eWZ+bG6I4LTp}Twc0wU>gOuF_^HT{VEY69*x^wu>40YgFdY+P(}0*ag!@^ z#Rfzd6o^wf+n%_Qpumaa4r%|qEUlcG#@;!A%#&W_9D&Tu)tOIh>I{#_yfy8Qx+1?U zkgtoU>0eElB5TYvHRRmz=i+D7(G|_+*y&qcXDc9q+n_QhDcUCWCNKlOszeRb-qPrGB`k%P`?eDpZ@;HG;=sUK;Rk=uSjKjb27pImIi6~p98xqgm#)?P^p?Bz zBJ+H`ma*qOjYtg$4qDchy<~XXzxA9&R>?xVi|fh#&IakkK5T1Vmj{D3M~tlk<60uG zmJ3t*a@|c$O!Ubi9u=B!x723OF>NfyA#Q}nCwH~_8vr=(hrVQ8{cR(bZT{{#kB!0B zL`pV%WRe{yJV12~#`{ObN7X6yLsd@36s0a`V8IrnREJS{=#D1R$?*Jhn7;xP`~BJp z|LFI)^!0UY5(Ytne9H-t-uQUaeE4$#=ap{tbk6vXC9fK3-x&`9Br2cSWI4Cfr7A#G z`t#(T{O8&rs|tu?&Ju5Wa{^Ly@O}0RZl{fc0XNNlSmdoan#>1AVTUja`qxix7B&pmV-mT{+eS37K?#2 z_2L6U#YfGXqwa22UlhN#a{N6oA7d(8-qUP&4StgdosN4a_xyP!uv<>KzTv>;;g;Xi zQ$t_mSTp#40LWiyIR8kBj|4o3_yM?B^G3c=3d2&6?7$kb9GjGg>|6cySK9>l#j{Ne zrXcj~mC*OE*^aT7$@Y7?#Wj5wj_a4JSDvb2*3K=UJejP0A(n9`gz_9BXnPxRbCu(+ zqN}lAhxG8^?G7Ckch>c@GqPaq$M2=F{UcYic{q&!JiRs6s**Z?*hYfB0f*$XX>IKc zRIlaL1LAY7A>@Nf-E)bf(mvCNA=5Jan5n3hk-g|AZh6SqIitY@3lv~@D|Bh@gSNSp zvO1P0^+66+@@ejB{&vpywt%Puxxnm+5c|dN5Lw;D-d@s4P3x)t5(*aFvlO}6k_acq za!iB_8@bJkoL<^YEN|1Hy0KC)tmiTrb`As)?{7@rmrLBRZ@+b`xZr>X<@F?s6uxVh zV~TcP8erBNH=hx*a9h})S(+{ipE5(V3@@$>r{`IW|Fea#DxrTLBQGxP1GTO%@!SO+ z`-IG1?Te!5LaQgj1*Y-2_t2(Ap6-5 zAB6KA-Cyy2+U8oVOwdzhJU+j|*MQYqb|)#tl?!OD`$>8{NN=RaxI}k^{H;|Ur&e8_ zW!0FlVHf!fFce8Gl`sBrY z_D;4Vn4lByH$6l2-Y^`d$YfjS63q#-W7-_1ymQ52(#G~?Zz}Z&fPQ|V<+ay04xx|R z`4=LTb1akW@XOy;-U@fodX2hhT7cbSPIoOZ)1$}hu1Zn0j5HNkTWzzy7E|_$_`h}g z2f1ZqYB69^rx#g5 z>wZPsm^rxgBhN2`3Se=$zyFae{@T=n#UgsVBVJ@WKRyL8`gG9G|e(0IxS*PRyHN2{MU*0ILonM*Iu5n2YEZCi^cGx>uM<#{N-9v|{Ke?*;$v^m3!P_Zi z8X=7?VFc#P80J9_7tL!T@&qYhI?@9Z=jY?UrWJ%I?J`-w;YBqSA^@7kyarxfS5McT<# z6fJm!ASIzHhLJQ)Q4rAvMmU((+GR!gP&PvG4)>ir@gu?X)nn8({-JAU{o;4J1Gua{ zbm_IeA>WiBQ;qDVMV`IA9sK~ICSK~j6TFfCHl+{jW>5xx^}ul()U zb7X|xL{+z1&y@!ilN(_YtV;OM*)Z~PdAn%>T_&6wC+CQdP#8iGQHt;J|8A=Ue3dEM zUViU1t2T)KXSE_4n@pP(`78hEFi6V9cK*i4yHT#@2AyzF;ath3<5A2W()nhs(?Z4pOL#eSblGPp7XAK z_oJuJkh7hW@Gp%n^x`@JmNI*D=BCd@89Zo6|M*WCHK8qmA%d-toCbRkR8wOp?ZUC za<)%-olWn5dns2^33(8nwfQ4~Rbevvotl@>X7W@KlP5+>zvVf9ILhDwGX+NFQc(A8 z(b!pLaTXSCj_D?&_1$O&p*#X2;bG- z+0Kzg9ZP{WMTMCe!hCs=54Bc6Sf-@jVC`#41cYhd1PHBCBp7N~!t~!yYeTN)Ta}bQ z{@qFZa52H9XZ?Wc9D^`{h_s2zk6(LRZZ!`NnfI`+HG6d%pfnz-EtPy45r29~!P^u< z;+0n1U-MTIJ^6jw6!zMlGgck6kFsyvjpSqTQ~8{N3Qd6l3CSo6kq91Q+aTvoyFz@U zOGCEm4c&N}RZPLVM0#t1K>cWHKY#TdXbd-+b7bhr%QUjFm)q7I zMd%|)kgJRg*DE1o&BjRutFg&nec&2Jg(2W;+7F;V>^#vKzvjD4WuLyMSq`U7nE$!k zw^^P+8M}wwyI01V%uR$5Tj{Tf3RykXbTuhQ`|(2-Uy3hkmJ*>4IM@(YR3oPr;fqSg zU}|J;8(W_tu#6Q2*#2H**P&x;K^8BoI?3 zsFl=8vpIZy`;nV(vC`cRCLx4sw!)lN0r2edOV;VrW~S@jIM^3mY=IX)fUScc?4^M; zEq;7?Jx0|hxYDASccN#*(u2+NluZo)BY8#evj|O4sLmJb zA*TI9nmCLC>6PY(!yM~nQgm1-(#IpZ9( zqHofaWY=M87-JK*p#(L01%p-t{HllfX)mr{re-~g$}>F19?{n3>XdaOu(tz z{Xh-xl)n#ECB|D+lDU%(TueJBx``2OK#dft2b9h$(L#St-JE@n!T5$nyX?vM=a05E zqVuW!Bm5GC!S%4q1Ut11nQ_a~^}~mvbLVZLS+ZuqZ!7Or{CL`-G_roueZj#2qW$T6iXaJeR-4?J|5XVeb?=?CqI(CT@&5wu3K8`joJ7T38F9vT zkM@ULpSpVVq|`BIYK>|oE#+oEIzm;81CR#;2L~C?Ow~7SMHHK{%|)xtd1h@xIO8Kfs3U7mjZL&sO6rRs0yJ~R!b!&@5(qf?vB#$vQImZKH5lyfMqm-& zh5Jpf&+`E24jbm=Zt0fi9A_jP_pz*8$21nA(pz?tFsI2`HyE+OA;%fP#tsHL#B#=p<$Scj3_!u-jE+A*K?H+6yo6Xr(v&u?`DVhMz!Ik;9e!h; zJ4Z|ZZ8g@oh-2@Vg1(U*hUFB|2UoGwhQ_&IHm-;7t>j zaxuptx`KoZWCPCt^GrqexR1$nju7b_q@a9{HvqDeyDV6aNY5DqzG@4nodnjfn75gB zZE^|Voy-FqfrE~{e+n7_u*(Iq!q6hRsw=!$D6DaihEIL*D*?BEf7sg6Ma z91aK`nd6>vdE>TfdX|;VNg1O^rt(%b^7b#!xftMs^BzBlj=1T@GnnMJSYjLe*rY~m zu~lLiFaW4I&)yio#|Ef|TWC>5yGlagM8VO?FUQ^ula8IbW9v~{>?}gc+kqJ>g|c&# zyC<+=$D#D8zN5C9lDSzRczp6Op_J@g^gl1-l6`yQrDil*mH4?!>5>#KKY0ONxd$Y2 zFgtU`J*uy!pp}gftg9F@ykar6T~5=-W9oS$B>MKKVsx1{6p$)Lc45SU`{aYyxWVb3 zxS?Lc>OEAPA|r+1jD{=DdSu}BJpTYbYBxrZJVtkn?DGqk-SZN8!*j_Wk8etl=s+c( z&WuSiN@I+Ss^ARd<7RlrO{9^KPdONoSeTq3RouV`^Y@P9%O2P`JP(`lHEVG_M@cDJ z0k)IQjO6^WfC$M8$RuYT!}%I&$OY1|a}%)KLdmp(N#h)C>EF{JoN5)&TunzO$qO=vmCc65V5PbXEIh=$n;^0T>DDdi`qbO47&s$kcI@uw$M& z=jqm(Y$XJRn!rx6hmu&41~$dC7U#bep&CSyF5(DLjIqfhw>hTUdKuclWM|w~*ujpIjVVg5!K`lPB?DXZ-U(S(5qK z3=1l5A5YesG^!kKIgyC@df@ut_3zS>T8_`Loq8^0zKQL31Vs)*e8q^zIT+(P{C_$> zv}ShP%A;zGGiM}cpTnAN$=ImEO++V z#zSHmJSnbN0oRfjLK6yU8@Hpqw+|`F?PbyP#5Sa)r z&yA#Ul6vF}V?OxrPi@I-T?qx%rL2-p6%1^4?U;Opv%pu$10{O#j&KbolRQ^x70h=K z#0X^t(C4EMZ*G0EMtY|!A*xcA!>@&<*oFfn6DVxUCe5TbLZP~m)4!;}Je;ZgJ7aM< zib5^d%P;1s1xO3mrzD@(rUp$o$w^#JGNj5UO1H56+>%W4M%dcsK&Q42(SeK;*VB(# z;kVP`zdn2tI>y)q*_#dL0=y(th^c+8wyV-N>L99q`$} z`sY8&NtbiZ&m?B&Bpsn62uV3?5&XS-V-;x}??SbrMrEB^H7~Hcp#fN6a7HtcoE}f( z+*_Y&qcz$zZ4lbI0JDyr2>_pL@ECpU3a+*#t7=23LkHMpIX0-O6Xa#w7-NDCPI5r& z&N_CetnVedwT3vEC3QJxn{El{8NP0W{{Xr(>D;oq4QxZG>Fap@U|CmlZHsJRL%3%n zw!x4HF3IeUq2ZQ_<~LPUxtZHhnu$=f6q&Id!EOncImK7C84QA0ylONKkEt5a&} zB*HvMC1+JDfHx4QB;ex%gZ}^m=KA4bX1IvXT}W4zj^rTWMpxzIgOR}sKA?`MDz_OD z!zD^MZ2CgWI+V9j^Vp-Z?SV;SuUbQm7X^Vh_RM%rIZf*ugW@(gnQzf1=1G=5ab*Yg}@ysl1Y*#TYwn`e6WLH##jOi9DY41 zHF2RXCXVLS{{Yt&-IZD4WGcZ_gVUVyGv5c(H4{LS!SY-LCl1QNm<9li!=4Utob}Hg z1p{#KBjY?C{{TM3l>*47&&hTb+N zRVd7;8Jz9vH0 zKD~29yQQ^0c3882%R|td{oSWN{{WCQt_0BBKiYEAG%-kU?Id7=Gmr*z-1}#m4712$ zGpCWc)dHQ7mibRi4oAv5cjWaVsWi|u;zcB?>4FFwzVj&@_XqH(ZVHhkK@p7D8%XPe z#zt|`x*p7ZXh=%{ zM?!h^>DLy#$=q?dmO!~>4p?K%B}q~bZas%ygXvFXEfc?Z)u1oXy# z8deWNJWFjEh;3_mPnf%3DC!Sh`57RO#NcyMN#{g}A95=;8FSCSJ!$kCT8)#-Hzhp9 k5fU-H;N$6@pGqz>zdM2Q$RSy94n1?*82*@{38sJl*$DN>zW@LL literal 0 HcmV?d00001 From 63b08905141f2c69c7ec13f64f05c7ac7f41e79b Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Fri, 8 Nov 2024 10:08:17 +0100 Subject: [PATCH 4/5] feat(personal-to-team): remove password field from confirmation step (WPB-11268) (#3607) --- .../teammigration/TeamMigrationState.kt | 1 - .../TeamMigrationConfirmationStepScreen.kt | 56 +++---------------- app/src/main/res/values/strings.xml | 2 - 3 files changed, 8 insertions(+), 51 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/teammigration/TeamMigrationState.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/teammigration/TeamMigrationState.kt index c91a99f479c..561ad8f57bd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/teammigration/TeamMigrationState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/teammigration/TeamMigrationState.kt @@ -21,6 +21,5 @@ import androidx.compose.foundation.text.input.TextFieldState data class TeamMigrationState( val teamNameTextState: TextFieldState = TextFieldState(), - val passwordTextState: TextFieldState = TextFieldState(), val shouldShowMigrationLeaveDialog: Boolean = false ) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/teammigration/step3/TeamMigrationConfirmationStepScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/teammigration/step3/TeamMigrationConfirmationStepScreen.kt index cede3cf1474..69fc9ca95e2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/teammigration/step3/TeamMigrationConfirmationStepScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/teammigration/step3/TeamMigrationConfirmationStepScreen.kt @@ -24,9 +24,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.input.TextFieldState -import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -37,8 +34,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle @@ -52,15 +47,13 @@ import com.wire.android.navigation.style.SlideNavigationAnimation import com.wire.android.ui.common.WireCheckbox import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions -import com.wire.android.ui.common.textfield.DefaultPassword -import com.wire.android.ui.common.textfield.WirePasswordTextField import com.wire.android.ui.destinations.TeamMigrationDoneStepScreenDestination import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireTypography -import com.wire.android.ui.userprofile.teammigration.common.BottomLineButtons -import com.wire.android.ui.userprofile.teammigration.common.BulletList import com.wire.android.ui.userprofile.teammigration.PersonalToTeamMigrationNavGraph import com.wire.android.ui.userprofile.teammigration.TeamMigrationViewModel +import com.wire.android.ui.userprofile.teammigration.common.BottomLineButtons +import com.wire.android.ui.userprofile.teammigration.common.BulletList import com.wire.android.util.CustomTabsHelper import com.wire.android.util.ui.PreviewMultipleThemes @@ -81,8 +74,7 @@ fun TeamMigrationConfirmationStepScreen( }, onBackPressed = { navigator.popBackStack() - }, - passwordTextState = teamMigrationViewModel.teamMigrationState.passwordTextState + } ) LaunchedEffect(Unit) { teamMigrationViewModel.sendPersonalTeamCreationFlowStartedEvent(3) @@ -91,7 +83,6 @@ fun TeamMigrationConfirmationStepScreen( @Composable private fun TeamMigrationConfirmationStepScreenContent( - passwordTextState: TextFieldState, modifier: Modifier = Modifier, onContinueButtonClicked: () -> Unit = { }, onBackPressed: () -> Unit = { } @@ -144,16 +135,9 @@ private fun TeamMigrationConfirmationStepScreenContent( ) BulletList(messages) - PasswordInput( - modifier = Modifier - .fillMaxWidth() - .padding( - top = dimensions().spacing56x, - bottom = dimensions().spacing56x - ), - passwordState = passwordTextState, - ) - Row { + Row( + modifier = Modifier.padding(top = dimensions().spacing48x) + ) { WireCheckbox( checked = agreedToMigrationTerms.value, onCheckedChange = { agreedToMigrationTerms.value = it } @@ -173,8 +157,7 @@ private fun TeamMigrationConfirmationStepScreenContent( WireTermsOfUseWithLink() } } - val isContinueButtonEnabled = - passwordTextState.text.isNotEmpty() && agreedToMigrationTerms.value && acceptedWireTermsOfUse.value + val isContinueButtonEnabled = agreedToMigrationTerms.value && acceptedWireTermsOfUse.value BottomLineButtons( isContinueButtonEnabled = isContinueButtonEnabled, onContinue = onContinueButtonClicked, @@ -211,33 +194,10 @@ private fun RowScope.WireTermsOfUseWithLink() { ) } -@Composable -private fun PasswordInput( - passwordState: TextFieldState, - modifier: Modifier = Modifier -) { - val keyboardController = LocalSoftwareKeyboardController.current - - WirePasswordTextField( - textState = passwordState, - labelText = stringResource(R.string.personal_to_team_migration_confirmation_step_password_field_label), - keyboardOptions = KeyboardOptions.DefaultPassword, - placeholderText = stringResource(R.string.personal_to_team_migration_confirmation_step_password_field_placeholder), - onKeyboardAction = { - keyboardController?.hide() - }, - modifier = modifier - .testTag("passwordFieldTeamMigration"), - testTag = "passwordFieldTeamMigration" - ) -} - @PreviewMultipleThemes @Composable private fun TeamMigrationConfirmationStepPreview() { WireTheme { - TeamMigrationConfirmationStepScreenContent( - passwordTextState = rememberTextFieldState() - ) + TeamMigrationConfirmationStepScreenContent() } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14350045b4f..b502e66fd1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1604,8 +1604,6 @@ In group conversations, the group admin can overwrite this setting. I agree to the migration terms and understand that this change is irreversible. I accept Wire\’s Terms of Use. - Password of your personal account - Enter password Congratulations %1$s! You\’re now the owner of the team %1$s. From 799d1e2627fdf9819c797ce3b70b2af6418ab2d5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:03:44 +0100 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20showing=20multiple=20calls=20at=20th?= =?UTF-8?q?e=20same=20time=20[WPB-10430]=20=F0=9F=8D=92=20(#3611)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Saleniuk <30429749+saleniuk@users.noreply.github.com> Co-authored-by: Michał Saleniuk --- .../notification/CallNotificationManager.kt | 119 +++++--- .../notification/NotificationConstants.kt | 14 +- .../notification/WireNotificationManager.kt | 38 ++- .../CallNotificationDismissedReceiver.kt | 6 +- .../DeclineIncomingCallReceiver.kt | 39 ++- .../com/wire/android/services/CallService.kt | 19 +- .../ui/calling/StartingCallActivity.kt | 36 ++- .../ui/calling/incoming/IncomingCallScreen.kt | 2 + .../ui/calling/ongoing/OngoingCallActivity.kt | 22 +- .../ui/calling/ongoing/OngoingCallScreen.kt | 2 + .../ui/calling/outgoing/OutgoingCallScreen.kt | 2 + .../ui/common/topappbar/CommonTopAppBar.kt | 234 +++++++++------ .../topappbar/CommonTopAppBarViewModel.kt | 52 ++-- .../common/topappbar/ConnectivityUIState.kt | 34 ++- .../kotlin/com/wire/android/util/LogUtil.kt | 27 ++ .../CallNotificationManagerTest.kt | 279 ++++++++++++++---- .../WireNotificationManagerTest.kt | 34 +-- .../topappbar/CommonTopAppBarViewModelTest.kt | 48 ++- 18 files changed, 676 insertions(+), 331 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/util/LogUtil.kt diff --git a/app/src/main/kotlin/com/wire/android/notification/CallNotificationManager.kt b/app/src/main/kotlin/com/wire/android/notification/CallNotificationManager.kt index 0b34dfa403a..aebd67bb1ed 100644 --- a/app/src/main/kotlin/com/wire/android/notification/CallNotificationManager.kt +++ b/app/src/main/kotlin/com/wire/android/notification/CallNotificationManager.kt @@ -21,10 +21,12 @@ package com.wire.android.notification import android.annotation.SuppressLint import android.app.Notification import android.content.Context +import android.service.notification.StatusBarNotification import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.wire.android.R import com.wire.android.appLogger +import com.wire.android.notification.NotificationConstants.INCOMING_CALL_ID_PREFIX import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.kalium.logic.data.call.Call import com.wire.kalium.logic.data.call.CallStatus @@ -34,7 +36,6 @@ import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.user.UserId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest @@ -42,9 +43,9 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.scan import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jetbrains.annotations.VisibleForTesting @@ -62,75 +63,108 @@ class CallNotificationManager @Inject constructor( private val notificationManager = NotificationManagerCompat.from(context) private val scope = CoroutineScope(SupervisorJob() + dispatcherProvider.default()) - private val incomingCallsForUsers = MutableStateFlow>(mapOf()) + private val incomingCallsForUsers = MutableStateFlow>(mapOf()) private val reloadCallNotification = MutableSharedFlow() init { scope.launch { incomingCallsForUsers .debounce { if (it.isEmpty()) 0L else DEBOUNCE_TIME } // debounce to avoid showing and hiding notification too fast - .map { it.entries.firstOrNull()?.toCallNotificationData() } + .map { + it.values.map { (userId, userName, calls) -> + calls.map { call -> + CallNotificationData(userId, call, userName) + } + }.flatten() + } + .scan(emptyList() to emptyList()) { (previousCalls, _), currentCalls -> + currentCalls to (currentCalls - previousCalls.toSet()) + } .distinctUntilChanged() - .reloadIfNeeded() - .collectLatest { incomingCallData -> - if (incomingCallData == null) { - hideIncomingCallNotification() - } else { - appLogger.i("$TAG: showing incoming call") - showIncomingCallNotification(incomingCallData) + .flatMapLatest { (allCurrentCalls, newCalls) -> + reloadCallNotification + .map { (userIdString, conversationIdString) -> + allCurrentCalls to allCurrentCalls.filter { // emit call that needs to be reloaded as newOrUpdated + it.userId.toString() == userIdString && it.conversationId.toString() == conversationIdString + } + } + .filter { (_, newCalls) -> newCalls.isNotEmpty() } // only emit if there is something to reload + .onStart { emit(allCurrentCalls to newCalls) } + } + .collectLatest { (allCurrentCalls, newCalls) -> + // remove outdated incoming call notifications + hideOutdatedIncomingCallNotifications(allCurrentCalls) + // show current incoming call notifications + appLogger.i("$TAG: showing ${newCalls.size} new incoming calls (all incoming calls: ${allCurrentCalls.size})") + newCalls.forEach { data -> + showIncomingCallNotification(data) } } } } - fun reloadIfNeeded(data: CallNotificationData): Flow = reloadCallNotification - .filter { reloadCallNotificationIds -> // check if the reload action is for the same call - reloadCallNotificationIds.userIdString == data.userId.toString() - && reloadCallNotificationIds.conversationIdString == data.conversationId.toString() + @VisibleForTesting + internal fun hideOutdatedIncomingCallNotifications(currentIncomingCalls: List) { + val currentIncomingCallNotificationIds = currentIncomingCalls.map { + NotificationConstants.getIncomingCallId(it.userId.toString(), it.conversationId.toString()) } - .map { data } - .onStart { emit(data) } - - private fun Flow.reloadIfNeeded(): Flow = this.flatMapLatest { callEntry -> - callEntry?.let { reloadIfNeeded(it) } ?: flowOf(null) + hideIncomingCallNotifications { _, id -> !currentIncomingCallNotificationIds.contains(id) } } fun reloadCallNotifications(reloadCallNotificationIds: CallNotificationIds) = scope.launch { reloadCallNotification.emit(reloadCallNotificationIds) } - fun handleIncomingCallNotifications(calls: List, userId: UserId) { + fun handleIncomingCalls(calls: List, userId: UserId, userName: String) { if (calls.isEmpty()) { - incomingCallsForUsers.update { it.filter { it.key != userId } } + incomingCallsForUsers.update { + it.minus(userId) + } } else { - incomingCallsForUsers.update { it.filter { it.key != userId } + (userId to calls.first()) } + incomingCallsForUsers.update { + it.plus(userId to IncomingCallsForUser(userId, userName, calls)) + } } } - fun hideAllNotifications() { - hideIncomingCallNotification() + private fun hideIncomingCallNotifications(predicate: (tag: String, id: Int) -> Boolean) { + notificationManager.activeNotifications.filter { + it.tag?.startsWith(INCOMING_CALL_ID_PREFIX) == true && predicate(it.tag, it.id) + }.forEach { + it.hideIncomingCallNotification() + } } - private fun hideIncomingCallNotification() { + fun hideAllIncomingCallNotifications() = hideIncomingCallNotifications { _, _ -> true } + + fun hideAllIncomingCallNotificationsForUser(userId: UserId) = + hideIncomingCallNotifications { tag, _ -> tag == NotificationConstants.getIncomingCallTag(userId.toString()) } + + fun hideIncomingCallNotification(userIdString: String, conversationIdString: String) = + hideIncomingCallNotifications { _, id -> id == NotificationConstants.getIncomingCallId(userIdString, conversationIdString) } + + private fun StatusBarNotification.hideIncomingCallNotification() { appLogger.i("$TAG: hiding incoming call") // This delay is just so when the user receives two calling signals one straight after the other [INCOMING -> CANCEL] - // Due to the signals being one after the other we are creating a notification when we are trying to cancel it, it wasn't properly - // cancelling vibration as probably when we were cancelling, the vibration object was still being created and started and thus - // never stopped. + // Due to the signals being one after the other we are creating a notification when we are trying to cancel it, it wasn't + // properly cancelling vibration as probably when we were cancelling, the vibration object was still being created and started + // and thus never stopped. TimeUnit.MILLISECONDS.sleep(CANCEL_CALL_NOTIFICATION_DELAY) - notificationManager.cancel(NotificationIds.CALL_INCOMING_NOTIFICATION_ID.ordinal) + notificationManager.cancel(tag, id) } @SuppressLint("MissingPermission") @VisibleForTesting internal fun showIncomingCallNotification(data: CallNotificationData) { - appLogger.i("$TAG: showing incoming call notification for user ${data.userId.toLogString()}") - val notification = builder.getIncomingCallNotification(data) - notificationManager.notify( - NotificationIds.CALL_INCOMING_NOTIFICATION_ID.ordinal, - notification + appLogger.i( + "$TAG: showing incoming call notification for user ${data.userId.toLogString()}" + + " and conversation ${data.conversationId.toLogString()}" ) + val tag = NotificationConstants.getIncomingCallTag(data.userId.toString()) + val id = NotificationConstants.getIncomingCallId(data.userId.toString(), data.conversationId.toString()) + val notification = builder.getIncomingCallNotification(data) + notificationManager.notify(tag, id, notification) } // Notifications @@ -141,10 +175,6 @@ class CallNotificationManager @Inject constructor( @VisibleForTesting internal const val DEBOUNCE_TIME = 200L - - fun hideIncomingCallNotification(context: Context) { - NotificationManagerCompat.from(context).cancel(NotificationIds.CALL_INCOMING_NOTIFICATION_ID.ordinal) - } } } @@ -164,6 +194,7 @@ class CallNotificationBuilder @Inject constructor( .setSmallIcon(R.drawable.notification_icon_small) .setContentTitle(data.conversationName) .setContentText(context.getString(R.string.notification_outgoing_call_tap_to_return)) + .setSubText(data.userName) .setAutoCancel(false) .setOngoing(true) .setSilent(true) @@ -188,6 +219,7 @@ class CallNotificationBuilder @Inject constructor( .setSmallIcon(R.drawable.notification_icon_small) .setContentTitle(title) .setContentText(content) + .setSubText(data.userName) .setAutoCancel(false) .setOngoing(true) .setVibrate(VIBRATE_PATTERN) @@ -214,6 +246,7 @@ class CallNotificationBuilder @Inject constructor( return NotificationCompat.Builder(context, channelId) .setContentTitle(title) .setContentText(context.getString(R.string.notification_ongoing_call_content)) + .setSubText(data.userName) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setCategory(NotificationCompat.CATEGORY_CALL) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) @@ -275,10 +308,13 @@ class CallNotificationBuilder @Inject constructor( } } +data class IncomingCallsForUser(val userId: UserId, val userName: String, val incomingCalls: List) + data class CallNotificationIds(val userIdString: String, val conversationIdString: String) data class CallNotificationData( val userId: QualifiedID, + val userName: String, val conversationId: ConversationId, val conversationName: String?, val conversationType: Conversation.Type, @@ -286,8 +322,9 @@ data class CallNotificationData( val callerTeamName: String?, val callStatus: CallStatus ) { - constructor(userId: UserId, call: Call) : this( + constructor(userId: UserId, call: Call, userName: String) : this( userId, + userName, call.conversationId, call.conversationName, call.conversationType, @@ -296,5 +333,3 @@ data class CallNotificationData( call.status ) } - -fun Map.Entry.toCallNotificationData() = CallNotificationData(userId = key, call = value) diff --git a/app/src/main/kotlin/com/wire/android/notification/NotificationConstants.kt b/app/src/main/kotlin/com/wire/android/notification/NotificationConstants.kt index 186cc6dbf92..fdbe058e373 100644 --- a/app/src/main/kotlin/com/wire/android/notification/NotificationConstants.kt +++ b/app/src/main/kotlin/com/wire/android/notification/NotificationConstants.kt @@ -52,6 +52,9 @@ object NotificationConstants { // MessagesSummaryNotification ID depends on User, use fun getMessagesSummaryId(userId: UserId) to get it private const val MESSAGE_SUMMARY_ID_STRING = "wire_messages_summary_notification" + private const val INCOMING_CALL_TAG_PREFIX = "wire_incoming_call_tag_" + const val INCOMING_CALL_ID_PREFIX = "wire_incoming_call_" + fun getConversationNotificationId(conversationIdString: String, userIdString: String) = (conversationIdString + userIdString).hashCode() fun getMessagesGroupKey(userId: UserId?): String = "$MESSAGE_GROUP_KEY_PREFIX${userId?.toString() ?: ""}" fun getMessagesSummaryId(userId: UserId): Int = "$MESSAGE_SUMMARY_ID_STRING$userId".hashCode() @@ -60,6 +63,10 @@ object NotificationConstants { fun getPingsChannelId(userId: UserId): String = getChanelIdForUser(userId, PING_CHANNEL_ID) fun getIncomingChannelId(userId: UserId): String = getChanelIdForUser(userId, INCOMING_CALL_CHANNEL_ID) fun getOutgoingChannelId(userId: UserId): String = getChanelIdForUser(userId, OUTGOING_CALL_CHANNEL_ID) + fun getIncomingCallId(userIdString: String, conversationIdString: String): Int = + "$INCOMING_CALL_ID_PREFIX${userIdString}_$conversationIdString".hashCode() + + fun getIncomingCallTag(userIdString: String): String = "$INCOMING_CALL_TAG_PREFIX$userIdString" /** * @return NotificationChannelId [String] specific for user, use it to post a notifications. @@ -72,7 +79,12 @@ object NotificationConstants { // Notification IDs (has to be unique!) enum class NotificationIds { - CALL_INCOMING_NOTIFICATION_ID, + @Suppress("unused") + @Deprecated( + message = "Do not use it, it's here just because we use .ordinal as ID and ID for the foreground service notification cannot be 0", + level = DeprecationLevel.ERROR + ) + ZERO_ID, CALL_OUTGOING_ONGOING_NOTIFICATION_ID, PERSISTENT_NOTIFICATION_ID, MESSAGE_SYNC_NOTIFICATION_ID, diff --git a/app/src/main/kotlin/com/wire/android/notification/WireNotificationManager.kt b/app/src/main/kotlin/com/wire/android/notification/WireNotificationManager.kt index a2361e0e7ae..85798080259 100644 --- a/app/src/main/kotlin/com/wire/android/notification/WireNotificationManager.kt +++ b/app/src/main/kotlin/com/wire/android/notification/WireNotificationManager.kt @@ -28,6 +28,7 @@ import com.wire.android.util.CurrentScreen import com.wire.android.util.CurrentScreenManager import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.lifecycle.ConnectionPolicyManager +import com.wire.android.util.logIfEmptyUserName import com.wire.kalium.logger.obfuscateId import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.id.ConversationId @@ -51,6 +52,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -249,7 +251,7 @@ class WireNotificationManager @Inject constructor( // and remove the notifications that were displayed previously appLogger.i("$TAG no Users -> hide all the notifications") messagesNotificationManager.hideAllNotifications() - callNotificationManager.hideAllNotifications() + callNotificationManager.hideAllIncomingCallNotifications() servicesManager.stopCallService() return @@ -297,6 +299,7 @@ class WireNotificationManager @Inject constructor( private fun stopObservingForUser(userId: UserId, observingJobs: ObservingJobs) { messagesNotificationManager.hideAllNotificationsForUser(userId) + callNotificationManager.hideAllIncomingCallNotificationsForUser(userId) observingJobs.userJobs[userId]?.cancelAll() observingJobs.userJobs.remove(userId) } @@ -336,20 +339,26 @@ class WireNotificationManager @Inject constructor( ) { appLogger.d("$TAG observe incoming calls") - coreLogic.getSessionScope(userId).observeE2EIRequired() - .map { it is E2EIRequiredResult.NoGracePeriod } - .distinctUntilChanged() - .flatMapLatest { isBlockedByE2EIRequired -> - if (isBlockedByE2EIRequired) { - appLogger.d("$TAG calls were blocked as E2EI is required") - flowOf(listOf()) - } else { - coreLogic.getSessionScope(userId).calls.getIncomingCalls() + coreLogic.getSessionScope(userId).let { userSessionScope -> + userSessionScope.observeE2EIRequired() + .map { it is E2EIRequiredResult.NoGracePeriod } + .distinctUntilChanged() + .flatMapLatest { isBlockedByE2EIRequired -> + if (isBlockedByE2EIRequired) { + appLogger.d("$TAG calls were blocked as E2EI is required") + flowOf(listOf()) + } else { + userSessionScope.calls.getIncomingCalls() + }.map { calls -> + userSessionScope.users.getSelfUser().first() + .also { it.logIfEmptyUserName() } + .let { it.handle ?: it.name ?: "" } to calls + } } - } - .collect { calls -> - callNotificationManager.handleIncomingCallNotifications(calls, userId) - } + .collect { (userName, calls) -> + callNotificationManager.handleIncomingCalls(calls, userId, userName) + } + } } /** @@ -366,6 +375,7 @@ class WireNotificationManager @Inject constructor( val selfUserNameState = coreLogic.getSessionScope(userId) .users .getSelfUser() + .onEach { it.logIfEmptyUserName() } .map { it.handle ?: it.name ?: "" } .distinctUntilChanged() .stateIn(scope) diff --git a/app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/CallNotificationDismissedReceiver.kt b/app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/CallNotificationDismissedReceiver.kt index ea1722efd00..ba618d33abf 100644 --- a/app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/CallNotificationDismissedReceiver.kt +++ b/app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/CallNotificationDismissedReceiver.kt @@ -24,6 +24,7 @@ import android.content.Intent import com.wire.android.appLogger import com.wire.android.notification.CallNotificationIds import com.wire.android.notification.CallNotificationManager +import com.wire.kalium.logger.obfuscateId import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -36,7 +37,10 @@ class CallNotificationDismissedReceiver : BroadcastReceiver() { // requires zero override fun onReceive(context: Context, intent: Intent) { val conversationIdString: String = intent.getStringExtra(EXTRA_CONVERSATION_ID) ?: return val userIdString: String = intent.getStringExtra(EXTRA_USER_ID) ?: return - appLogger.i("CallNotificationDismissedReceiver: onReceive") + appLogger.i( + "CallNotificationDismissedReceiver: onReceive for user ${userIdString.obfuscateId()}" + + " and conversation ${conversationIdString.obfuscateId()}" + ) callNotificationManager.reloadCallNotifications(CallNotificationIds(userIdString, conversationIdString)) } diff --git a/app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DeclineIncomingCallReceiver.kt b/app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DeclineIncomingCallReceiver.kt index 3837e4212b8..26015086595 100644 --- a/app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DeclineIncomingCallReceiver.kt +++ b/app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DeclineIncomingCallReceiver.kt @@ -27,11 +27,11 @@ import com.wire.android.di.KaliumCoreLogic import com.wire.android.di.NoSession import com.wire.android.notification.CallNotificationManager import com.wire.android.util.dispatchers.DispatcherProvider +import com.wire.kalium.logger.obfuscateId import com.wire.kalium.logic.CoreLogic -import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.data.id.toQualifiedID -import com.wire.kalium.logic.feature.session.CurrentSessionResult +import com.wire.kalium.logic.data.user.UserId import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -56,27 +56,22 @@ class DeclineIncomingCallReceiver : BroadcastReceiver() { // requires zero argum @ApplicationScope lateinit var coroutineScope: CoroutineScope + @Inject + lateinit var callNotificationManager: CallNotificationManager + override fun onReceive(context: Context, intent: Intent) { - val conversationId: String = intent.getStringExtra(EXTRA_CONVERSATION_ID) ?: return - appLogger.i("CallNotificationDismissReceiver: onReceive, conversationId: $conversationId") + val conversationIdString: String = intent.getStringExtra(EXTRA_CONVERSATION_ID) ?: run { + appLogger.e("CallNotificationDismissReceiver: onReceive, conversation ID is missing") + return + } + appLogger.i("CallNotificationDismissReceiver: onReceive, conversationId: ${conversationIdString.obfuscateId()}") + val userId: UserId = intent.getStringExtra(EXTRA_RECEIVER_USER_ID)?.toQualifiedID(qualifiedIdMapper) ?: run { + appLogger.e("CallNotificationDismissReceiver: onReceive, user ID is missing") + return + } coroutineScope.launch(Dispatchers.Default) { - val userId: QualifiedID? = intent.getStringExtra(EXTRA_RECEIVER_USER_ID)?.toQualifiedID(qualifiedIdMapper) - val sessionScope = - if (userId != null) { - coreLogic.getSessionScope(userId) - } else { - val currentSession = coreLogic.globalScope { session.currentSession() } - if (currentSession is CurrentSessionResult.Success) { - coreLogic.getSessionScope(currentSession.accountInfo.userId) - } else { - null - } - } - - sessionScope?.let { - it.calls.rejectCall(qualifiedIdMapper.fromStringToQualifiedID(conversationId)) - } - CallNotificationManager.hideIncomingCallNotification(context) + coreLogic.getSessionScope(userId).calls.rejectCall(conversationIdString.toQualifiedID(qualifiedIdMapper)) + callNotificationManager.hideIncomingCallNotification(userId.toString(), conversationIdString) } } @@ -84,7 +79,7 @@ class DeclineIncomingCallReceiver : BroadcastReceiver() { // requires zero argum private const val EXTRA_CONVERSATION_ID = "conversation_id_extra" private const val EXTRA_RECEIVER_USER_ID = "user_id_extra" - fun newIntent(context: Context, conversationId: String?, userId: String?): Intent = + fun newIntent(context: Context, conversationId: String, userId: String): Intent = Intent(context, DeclineIncomingCallReceiver::class.java).apply { putExtra(EXTRA_CONVERSATION_ID, conversationId) putExtra(EXTRA_RECEIVER_USER_ID, userId) diff --git a/app/src/main/kotlin/com/wire/android/services/CallService.kt b/app/src/main/kotlin/com/wire/android/services/CallService.kt index 087a77ac960..1c866bd8793 100644 --- a/app/src/main/kotlin/com/wire/android/services/CallService.kt +++ b/app/src/main/kotlin/com/wire/android/services/CallService.kt @@ -32,12 +32,12 @@ import com.wire.android.notification.CallNotificationData import com.wire.android.notification.CallNotificationManager import com.wire.android.notification.NotificationIds import com.wire.android.util.dispatchers.DispatcherProvider +import com.wire.android.util.logIfEmptyUserName import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.call.CallStatus import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.feature.session.CurrentSessionResult import com.wire.kalium.logic.functional.Either -import com.wire.kalium.logic.functional.flatMapRight import com.wire.kalium.logic.functional.fold import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope @@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch @@ -101,10 +102,9 @@ class CallService : Service() { .flatMapLatest { if (it is CurrentSessionResult.Success && it.accountInfo.isValid()) { val userId = it.accountInfo.userId - val outgoingCallsFlow = - coreLogic.getSessionScope(userId).calls.observeOutgoingCall() - val establishedCallsFlow = - coreLogic.getSessionScope(userId).calls.establishedCall() + val userSessionScope = coreLogic.getSessionScope(userId) + val outgoingCallsFlow = userSessionScope.calls.observeOutgoingCall() + val establishedCallsFlow = userSessionScope.calls.establishedCall() combine( outgoingCallsFlow, @@ -112,7 +112,10 @@ class CallService : Service() { ) { outgoingCalls, establishedCalls -> val calls = outgoingCalls + establishedCalls calls.firstOrNull()?.let { call -> - Either.Right(CallNotificationData(userId, call)) + val userName = userSessionScope.users.getSelfUser().first() + .also { it.logIfEmptyUserName() } + .let { it.handle ?: it.name ?: "" } + Either.Right(CallNotificationData(userId, call, userName)) } ?: Either.Left("no calls") } } else { @@ -120,9 +123,7 @@ class CallService : Service() { } } .distinctUntilChanged() - .flatMapRight { callData -> - callNotificationManager.reloadIfNeeded(callData) - }.debounce { + .debounce { if (it is Either.Left) ServicesManager.DEBOUNCE_TIME else 0L } .collectLatest { diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/StartingCallActivity.kt b/app/src/main/kotlin/com/wire/android/ui/calling/StartingCallActivity.kt index b5dc7d2cb47..56cbe581ffc 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/StartingCallActivity.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/StartingCallActivity.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.semantics @@ -62,6 +63,23 @@ class StartingCallActivity : CallActivity() { @Inject lateinit var proximitySensorManager: ProximitySensorManager + var conversationId: String? by mutableStateOf(null) + var userId: String? by mutableStateOf(null) + var screenType: StartingCallScreenType? by mutableStateOf(null) + + private fun handleNewIntent(intent: Intent) { + conversationId = intent.extras?.getString(EXTRA_CONVERSATION_ID) + userId = intent.extras?.getString(EXTRA_USER_ID) + screenType = intent.extras?.getString(EXTRA_SCREEN_TYPE)?.let { StartingCallScreenType.byName(it) } + switchAccountIfNeeded(userId) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handleNewIntent(intent) + setIntent(intent) + } + @Suppress("LongMethod") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -71,10 +89,7 @@ class StartingCallActivity : CallActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) - val conversationId = intent.extras?.getString(EXTRA_CONVERSATION_ID) - val screenType = intent.extras?.getString(EXTRA_SCREEN_TYPE) - val userId = intent.extras?.getString(EXTRA_USER_ID) - switchAccountIfNeeded(userId) + handleNewIntent(intent) appLogger.i("$TAG Initializing proximity sensor..") proximitySensorManager.initialize() @@ -86,8 +101,7 @@ class StartingCallActivity : CallActivity() { LocalActivity provides this ) { WireTheme { - val currentCallScreenType by remember { mutableStateOf(StartingCallScreenType.byName(screenType)) } - currentCallScreenType?.let { currentScreenType -> + screenType?.let { currentScreenType -> AnimatedContent( targetState = currentScreenType, transitionSpec = { @@ -102,10 +116,7 @@ class StartingCallActivity : CallActivity() { when (screenType) { StartingCallScreenType.Outgoing -> { OutgoingCallScreen( - conversationId = - qualifiedIdMapper.fromStringToQualifiedID( - it - ) + conversationId = qualifiedIdMapper.fromStringToQualifiedID(it) ) { getOngoingCallIntent(this@StartingCallActivity, it).run { this@StartingCallActivity.startActivity(this) @@ -114,15 +125,16 @@ class StartingCallActivity : CallActivity() { } } - StartingCallScreenType.Incoming -> + StartingCallScreenType.Incoming -> { IncomingCallScreen( - qualifiedIdMapper.fromStringToQualifiedID(it) + conversationId = qualifiedIdMapper.fromStringToQualifiedID(it) ) { this@StartingCallActivity.startActivity( getOngoingCallIntent(this@StartingCallActivity, it) ) this@StartingCallActivity.finishAndRemoveTask() } + } } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt index f811904738d..43c9138270b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt @@ -64,9 +64,11 @@ import com.wire.kalium.logic.data.id.ConversationId fun IncomingCallScreen( conversationId: ConversationId, incomingCallViewModel: IncomingCallViewModel = hiltViewModel( + key = "incoming_$conversationId", creationCallback = { factory -> factory.create(conversationId = conversationId) } ), sharedCallingViewModel: SharedCallingViewModel = hiltViewModel( + key = "shared_$conversationId", creationCallback = { factory -> factory.create(conversationId = conversationId) } ), onCallAccepted: () -> Unit diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallActivity.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallActivity.kt index d318d103310..4a407e84e1d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallActivity.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallActivity.kt @@ -30,7 +30,10 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.animation.togetherWith import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.semantics @@ -66,6 +69,21 @@ class OngoingCallActivity : CallActivity() { @Inject lateinit var proximitySensorManager: ProximitySensorManager + var conversationId: String? by mutableStateOf(null) + var userId: String? by mutableStateOf(null) + + private fun handleNewIntent(intent: Intent) { + conversationId = intent.extras?.getString(EXTRA_CONVERSATION_ID) + userId = intent.extras?.getString(EXTRA_USER_ID) + switchAccountIfNeeded(userId) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handleNewIntent(intent) + setIntent(intent) + } + @SuppressLint("UnusedContentLambdaTargetStateParameter") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -74,9 +92,7 @@ class OngoingCallActivity : CallActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) - val conversationId = intent.extras?.getString(EXTRA_CONVERSATION_ID) - val userId = intent.extras?.getString(EXTRA_USER_ID) - switchAccountIfNeeded(userId) + handleNewIntent(intent) appLogger.i("$TAG Initializing proximity sensor..") proximitySensorManager.initialize() diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt index fae2471ac08..f9877a1fa6d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt @@ -107,9 +107,11 @@ import java.util.Locale fun OngoingCallScreen( conversationId: ConversationId, ongoingCallViewModel: OngoingCallViewModel = hiltViewModel( + key = "ongoing_$conversationId", creationCallback = { factory -> factory.create(conversationId = conversationId) } ), sharedCallingViewModel: SharedCallingViewModel = hiltViewModel( + key = "shared_$conversationId", creationCallback = { factory -> factory.create(conversationId = conversationId) } ) ) { diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/outgoing/OutgoingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/outgoing/OutgoingCallScreen.kt index 868b84907d8..b579a7e731a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/outgoing/OutgoingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/outgoing/OutgoingCallScreen.kt @@ -55,9 +55,11 @@ import com.wire.kalium.logic.data.id.ConversationId fun OutgoingCallScreen( conversationId: ConversationId, sharedCallingViewModel: SharedCallingViewModel = hiltViewModel( + key = "shared_$conversationId", creationCallback = { factory -> factory.create(conversationId = conversationId) } ), outgoingCallViewModel: OutgoingCallViewModel = hiltViewModel( + key = "outgoing_$conversationId", creationCallback = { factory -> factory.create(conversationId = conversationId) } ), onCallAccepted: () -> Unit diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt index 2a40d0e0305..c22b5c60996 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt @@ -30,7 +30,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -64,9 +64,9 @@ import com.wire.kalium.network.NetworkState fun CommonTopAppBar( themeOption: ThemeOption, commonTopAppBarState: CommonTopAppBarState, - onReturnToCallClick: (ConnectivityUIState.EstablishedCall) -> Unit, - onReturnToIncomingCallClick: (ConnectivityUIState.IncomingCall) -> Unit, - onReturnToOutgoingCallClick: (ConnectivityUIState.OutgoingCall) -> Unit, + onReturnToCallClick: (ConnectivityUIState.Call.Established) -> Unit, + onReturnToIncomingCallClick: (ConnectivityUIState.Call.Incoming) -> Unit, + onReturnToOutgoingCallClick: (ConnectivityUIState.Call.Outgoing) -> Unit, modifier: Modifier = Modifier, ) { Column(modifier = modifier) { @@ -84,9 +84,7 @@ fun CommonTopAppBar( @Composable fun getBackgroundColor(connectivityInfo: ConnectivityUIState): Color { return when (connectivityInfo) { - is ConnectivityUIState.EstablishedCall, - is ConnectivityUIState.IncomingCall, - is ConnectivityUIState.OutgoingCall -> MaterialTheme.wireColorScheme.positive + is ConnectivityUIState.Calls -> MaterialTheme.wireColorScheme.positive is ConnectivityUIState.WaitingConnection, ConnectivityUIState.Connecting -> MaterialTheme.wireColorScheme.primary @@ -100,9 +98,9 @@ private fun ConnectivityStatusBar( themeOption: ThemeOption, connectivityInfo: ConnectivityUIState, networkState: NetworkState, - onReturnToCallClick: (ConnectivityUIState.EstablishedCall) -> Unit, - onReturnToIncomingCallClick: (ConnectivityUIState.IncomingCall) -> Unit, - onReturnToOutgoingCallClick: (ConnectivityUIState.OutgoingCall) -> Unit + onReturnToCallClick: (ConnectivityUIState.Call.Established) -> Unit, + onReturnToIncomingCallClick: (ConnectivityUIState.Call.Incoming) -> Unit, + onReturnToOutgoingCallClick: (ConnectivityUIState.Call.Outgoing) -> Unit, ) { val isVisible = connectivityInfo !is ConnectivityUIState.None val backgroundColor = getBackgroundColor(connectivityInfo) @@ -124,54 +122,28 @@ private fun ConnectivityStatusBar( ClearStatusBarColor() } - val barModifier = Modifier - .animateContentSize() - .fillMaxWidth() - .height(MaterialTheme.wireDimensions.ongoingCallLabelHeight) - .background(backgroundColor) - .run { - when (connectivityInfo) { - is ConnectivityUIState.EstablishedCall -> clickable(onClick = { - onReturnToCallClick( - connectivityInfo - ) - }) - - is ConnectivityUIState.IncomingCall -> clickable(onClick = { - onReturnToIncomingCallClick( - connectivityInfo - ) - }) - - is ConnectivityUIState.OutgoingCall -> clickable(onClick = { - onReturnToOutgoingCallClick( - connectivityInfo - ) - }) - - else -> this - } - } - AnimatedVisibility( visible = isVisible, enter = expandIn(initialSize = { fullSize -> IntSize(fullSize.width, 0) }), exit = shrinkOut(targetSize = { fullSize -> IntSize(fullSize.width, 0) }) ) { Column( - modifier = barModifier, + modifier = Modifier + .animateContentSize() + .fillMaxWidth() + .heightIn(min = MaterialTheme.wireDimensions.ongoingCallLabelHeight) + .background(backgroundColor), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { when (connectivityInfo) { - is ConnectivityUIState.EstablishedCall -> - OngoingCallContent(connectivityInfo.isMuted) - - is ConnectivityUIState.IncomingCall -> - IncomingCallContent(callerName = connectivityInfo.callerName) - - is ConnectivityUIState.OutgoingCall -> - OutgoingCallContent(conversationName = connectivityInfo.conversationName) + is ConnectivityUIState.Calls -> + CallsContent( + calls = connectivityInfo.calls, + onReturnToCallClick = onReturnToCallClick, + onReturnToIncomingCallClick = onReturnToIncomingCallClick, + onReturnToOutgoingCallClick = onReturnToOutgoingCallClick + ) ConnectivityUIState.Connecting -> StatusLabel( @@ -237,34 +209,102 @@ private fun WaitingStatusLabelInternal( } @Composable -private fun OngoingCallContent(isMuted: Boolean) { - Row { +private fun CallsContent( + calls: List, + onReturnToCallClick: (ConnectivityUIState.Call.Established) -> Unit, + onReturnToIncomingCallClick: (ConnectivityUIState.Call.Incoming) -> Unit, + onReturnToOutgoingCallClick: (ConnectivityUIState.Call.Outgoing) -> Unit, +) { + Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.wireDimensions.spacing12x)) { + calls.forEach { call -> + when (call) { + is ConnectivityUIState.Call.Established -> OngoingCallContent( + isMuted = call.isMuted, + modifier = Modifier + .clickable( + onClick = remember(call) { + { + onReturnToCallClick(call) + } + } + ) + .fillMaxWidth() + .heightIn(min = MaterialTheme.wireDimensions.ongoingCallLabelHeight) + ) + + is ConnectivityUIState.Call.Incoming -> IncomingCallContent( + callerName = call.callerName, + modifier = Modifier + .clickable( + onClick = remember(call) { + { + onReturnToIncomingCallClick(call) + } + } + ) + .fillMaxWidth() + .heightIn(min = MaterialTheme.wireDimensions.ongoingCallLabelHeight) + ) + + is ConnectivityUIState.Call.Outgoing -> OutgoingCallContent( + conversationName = call.conversationName, + modifier = Modifier + .clickable( + onClick = remember(call) { + { + onReturnToOutgoingCallClick(call) + } + } + ) + .fillMaxWidth() + .heightIn(min = MaterialTheme.wireDimensions.ongoingCallLabelHeight) + ) + } + } + } +} + +@Composable +private fun OngoingCallContent(isMuted: Boolean, modifier: Modifier = Modifier) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier, + ) { MicrophoneIcon(isMuted, MaterialTheme.wireColorScheme.onPositive) CameraIcon(MaterialTheme.wireColorScheme.onPositive) StatusLabel( - R.string.connectivity_status_bar_return_to_call, - MaterialTheme.wireColorScheme.onPositive + stringResource = R.string.connectivity_status_bar_return_to_call, + color = MaterialTheme.wireColorScheme.onPositive, ) } } @Composable -private fun IncomingCallContent(callerName: String?) { - Row { +private fun IncomingCallContent(callerName: String?, modifier: Modifier = Modifier) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier, + ) { StatusLabelWithValue( stringResource = R.string.connectivity_status_bar_return_to_incoming_call, - callerName = callerName, + callerName = callerName ?: stringResource(R.string.username_unavailable_label), color = MaterialTheme.wireColorScheme.onPositive ) } } @Composable -private fun OutgoingCallContent(conversationName: String?) { - Row { +private fun OutgoingCallContent(conversationName: String?, modifier: Modifier = Modifier) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier, + ) { StatusLabelWithValue( stringResource = R.string.connectivity_status_bar_return_to_outgoing_call, - callerName = conversationName, + callerName = conversationName ?: stringResource(R.string.username_unavailable_label), color = MaterialTheme.wireColorScheme.onPositive ) } @@ -272,25 +312,26 @@ private fun OutgoingCallContent(conversationName: String?) { @Composable private fun StatusLabel( - stringResource: Int, + string: String, color: Color = MaterialTheme.wireColorScheme.onPrimary ) { - StatusLabel( - string = stringResource(id = stringResource), + Text( + text = string.uppercase(), color = color, + style = MaterialTheme.wireTypography.title03, + textAlign = TextAlign.Center, + modifier = Modifier.padding(vertical = MaterialTheme.wireDimensions.spacing6x) ) } @Composable private fun StatusLabel( - string: String, + stringResource: Int, color: Color = MaterialTheme.wireColorScheme.onPrimary ) { - Text( - text = string.uppercase(), + StatusLabel( + string = stringResource(id = stringResource), color = color, - style = MaterialTheme.wireTypography.title03, - textAlign = TextAlign.Center, ) } @@ -305,6 +346,7 @@ private fun StatusLabelWithValue( text = stringResource(id = stringResource, callerName ?: defaultCallerName).uppercase(), color = color, style = MaterialTheme.wireTypography.title03, + modifier = Modifier.padding(vertical = MaterialTheme.wireDimensions.spacing6x) ) } @@ -351,44 +393,36 @@ private fun ClearStatusBarColor() { } @Composable -private fun PreviewCommonTopAppBar(connectivityUIState: ConnectivityUIState) { - WireTheme { - CommonTopAppBar(ThemeOption.SYSTEM, CommonTopAppBarState(connectivityUIState), {}, {}, {}) - } +private fun PreviewCommonTopAppBar(connectivityUIState: ConnectivityUIState) = WireTheme { + CommonTopAppBar(ThemeOption.SYSTEM, CommonTopAppBarState(connectivityUIState), {}, {}, {}) } @PreviewMultipleThemes @Composable -fun PreviewCommonTopAppBar_ConnectivityCallNotMuted() = +fun PreviewCommonTopAppBar_ConnectivityEstablishedCallNotMuted() = PreviewCommonTopAppBar( - ConnectivityUIState.EstablishedCall( - ConversationId("what", "ever"), - false - ) + ConnectivityUIState.Calls(listOf(ConnectivityUIState.Call.Established(ConversationId("what", "ever"), false))) ) @PreviewMultipleThemes @Composable -fun PreviewCommonTopAppBar_ConnectivityConnecting() = - PreviewCommonTopAppBar(ConnectivityUIState.Connecting) - -@PreviewMultipleThemes -@Composable -fun PreviewCommonTopAppBar_ConnectivityWaitingConnection() = - PreviewCommonTopAppBar(ConnectivityUIState.WaitingConnection(null, null)) - -@PreviewMultipleThemes -@Composable -fun PreviewCommonTopAppBar_ConnectivityNone() = - PreviewCommonTopAppBar(ConnectivityUIState.None) +fun PreviewCommonTopAppBar_ConnectivityEstablishedCallAndIncomingCalls() = + PreviewCommonTopAppBar( + ConnectivityUIState.Calls( + listOf( + ConnectivityUIState.Call.Established(ConversationId("1", "1"), false), + ConnectivityUIState.Call.Incoming(ConversationId("2", "2"), "John Doe"), + ConnectivityUIState.Call.Incoming(ConversationId("3", "3"), "Adam Smith"), + ) + ) + ) @PreviewMultipleThemes @Composable fun PreviewCommonTopAppBar_ConnectivityIncomingCall() = PreviewCommonTopAppBar( - ConnectivityUIState.IncomingCall( - ConversationId("what", "ever"), - "callerName" + ConnectivityUIState.Calls( + listOf(ConnectivityUIState.Call.Incoming(ConversationId("2", "2"), "John Doe")) ) ) @@ -396,8 +430,22 @@ fun PreviewCommonTopAppBar_ConnectivityIncomingCall() = @Composable fun PreviewCommonTopAppBar_ConnectivityOutgoingCall() = PreviewCommonTopAppBar( - ConnectivityUIState.OutgoingCall( - ConversationId("what", "ever"), - "conversationName" + ConnectivityUIState.Calls( + listOf(ConnectivityUIState.Call.Outgoing(ConversationId("2", "2"), "John Doe")) ) ) + +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityConnecting() = + PreviewCommonTopAppBar(ConnectivityUIState.Connecting) + +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityWaitingConnection() = + PreviewCommonTopAppBar(ConnectivityUIState.WaitingConnection(null, null)) + +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityNone() = + PreviewCommonTopAppBar(ConnectivityUIState.None) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt index d311d288704..0e58a698ce1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt @@ -70,7 +70,7 @@ class CommonTopAppBarViewModel @Inject constructor( } @VisibleForTesting - internal suspend fun activeCallFlow(userId: UserId): Flow = + internal suspend fun activeCallsFlow(userId: UserId): Flow> = coreLogic.sessionScope(userId) { combine( calls.establishedCall(), @@ -78,8 +78,6 @@ class CommonTopAppBarViewModel @Inject constructor( calls.observeOutgoingCall(), ) { establishedCall, incomingCalls, outgoingCalls -> establishedCall + incomingCalls + outgoingCalls - }.map { calls -> - calls.firstOrNull() }.distinctUntilChanged() } @@ -96,11 +94,11 @@ class CommonTopAppBarViewModel @Inject constructor( is CurrentSessionResult.Success -> { val userId = it.accountInfo.userId combine( - activeCallFlow(userId), + activeCallsFlow(userId), currentScreenFlow(), connectivityFlow(userId), - ) { activeCall, currentScreen, connectivity -> - mapToConnectivityUIState(currentScreen, connectivity, activeCall) + ) { activeCalls, currentScreen, connectivity -> + mapToConnectivityUIState(currentScreen, connectivity, activeCalls) } } } @@ -112,7 +110,7 @@ class CommonTopAppBarViewModel @Inject constructor( * could be called when the screen is changed, so we delayed * showing the banner until getting the correct calling values */ - if (connectivityUIState is ConnectivityUIState.EstablishedCall) { + if (connectivityUIState is ConnectivityUIState.Calls && connectivityUIState.hasOngoingCall) { delay(WAITING_TIME_TO_SHOW_ONGOING_CALL_BANNER) } state = state.copy(connectivityState = connectivityUIState) @@ -127,34 +125,24 @@ class CommonTopAppBarViewModel @Inject constructor( private fun mapToConnectivityUIState( currentScreen: CurrentScreen, connectivity: Connectivity, - activeCall: Call? + activeCalls: List, ): ConnectivityUIState { val canDisplayConnectivityIssues = currentScreen !is CurrentScreen.AuthRelated - - if (activeCall != null) { - return when (activeCall.status) { - CallStatus.INCOMING -> { - ConnectivityUIState.IncomingCall( - activeCall.conversationId, - activeCall.callerName - ) - } - - CallStatus.STARTED -> { - ConnectivityUIState.OutgoingCall( - activeCall.conversationId, - activeCall.conversationName - ) - } - - else -> { - ConnectivityUIState.EstablishedCall( - activeCall.conversationId, - activeCall.isMuted - ) - } - } + if (activeCalls.isNotEmpty()) { + return ConnectivityUIState.Calls( + calls = activeCalls.partition { it.status != CallStatus.INCOMING } + .let { (outgoingAndEstablished, incoming) -> + // outgoing and established first + (outgoingAndEstablished + incoming).map { call -> + when (call.status) { + CallStatus.INCOMING -> ConnectivityUIState.Call.Incoming(call.conversationId, call.callerName) + CallStatus.STARTED -> ConnectivityUIState.Call.Outgoing(call.conversationId, call.conversationName) + else -> ConnectivityUIState.Call.Established(call.conversationId, call.isMuted) + } + } + } + ) } return if (canDisplayConnectivityIssues) { diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt index 0730053e6d3..162ee0d71a0 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt @@ -31,18 +31,24 @@ sealed interface ConnectivityUIState { data object None : ConnectivityUIState - data class EstablishedCall( - val conversationId: ConversationId, - val isMuted: Boolean - ) : ConnectivityUIState - - data class IncomingCall( - val conversationId: ConversationId, - val callerName: String? - ) : ConnectivityUIState - - data class OutgoingCall( - val conversationId: ConversationId, - val conversationName: String? - ) : ConnectivityUIState + data class Calls(val calls: List) : ConnectivityUIState { + val hasOngoingCall: Boolean = calls.any { it is Call.Established } + } + + sealed interface Call { + data class Established( + val conversationId: ConversationId, + val isMuted: Boolean + ) : Call + + data class Incoming( + val conversationId: ConversationId, + val callerName: String? + ) : Call + + data class Outgoing( + val conversationId: ConversationId, + val conversationName: String? + ) : Call + } } diff --git a/app/src/main/kotlin/com/wire/android/util/LogUtil.kt b/app/src/main/kotlin/com/wire/android/util/LogUtil.kt new file mode 100644 index 00000000000..1907488de09 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/LogUtil.kt @@ -0,0 +1,27 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.util + +import com.wire.android.appLogger +import com.wire.kalium.logic.data.user.SelfUser + +fun SelfUser.logIfEmptyUserName() { + if (name.isNullOrBlank() && handle.isNullOrBlank()) { + appLogger.e("Name and handle is empty for self user with id ${id.toLogString()}") + } +} diff --git a/app/src/test/kotlin/com/wire/android/notification/CallNotificationManagerTest.kt b/app/src/test/kotlin/com/wire/android/notification/CallNotificationManagerTest.kt index ca449cd21e1..c98e8ca99ad 100644 --- a/app/src/test/kotlin/com/wire/android/notification/CallNotificationManagerTest.kt +++ b/app/src/test/kotlin/com/wire/android/notification/CallNotificationManagerTest.kt @@ -19,6 +19,7 @@ package com.wire.android.notification import android.app.Notification import android.content.Context +import android.service.notification.StatusBarNotification import androidx.core.app.NotificationManagerCompat import com.wire.android.config.TestDispatcherProvider import com.wire.android.notification.CallNotificationManager.Companion.DEBOUNCE_TIME @@ -47,20 +48,46 @@ class CallNotificationManagerTest { val dispatcherProvider = TestDispatcherProvider() @Test - fun `given no incoming calls, then hide notification`() = + fun `given no incoming calls but when there is still active incoming call notification, then hide that notification`() = runTest(dispatcherProvider.main()) { // given + val id = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val tag = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val userName = "user name" val (arrangement, callNotificationManager) = Arrangement() + .withActiveNotifications(listOf(mockStatusBarNotification(id, tag))) .arrange() - callNotificationManager.handleIncomingCallNotifications(listOf(), TEST_USER_ID1) + // when + callNotificationManager.handleIncomingCalls(listOf(), TEST_USER_ID1, userName) advanceUntilIdle() // then - verify(exactly = 0) { - arrangement.notificationManager.notify(NotificationIds.CALL_INCOMING_NOTIFICATION_ID.ordinal, any()) - } - verify(exactly = 1) { - arrangement.notificationManager.cancel(NotificationIds.CALL_INCOMING_NOTIFICATION_ID.ordinal) - } + verify(exactly = 0) { arrangement.notificationManager.notify(any(), any(), any()) } + verify(exactly = 1) { arrangement.notificationManager.cancel(tag, id) } + } + + @Test + fun `given incoming call, when call notification needs to be reloaded, then show that notification again`() = + runTest(dispatcherProvider.main()) { + // given + val notification = mockk() + val userName = "user name" + val callNotificationData = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName) + val id = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val tag = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val reloadCallIds = CallNotificationIds(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val (arrangement, callNotificationManager) = Arrangement() + .withIncomingNotificationForUserAndCall(notification, callNotificationData) + .arrange() + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName) + arrangement.withActiveNotifications(listOf(mockStatusBarNotification(id, tag))) + advanceUntilIdle() + arrangement.clearRecordedCallsForNotificationManager() // clear first empty list recorded call + // when + callNotificationManager.reloadCallNotifications(reloadCallIds) + advanceUntilIdle() + // then + verify(exactly = 1) { arrangement.notificationManager.notify(tag, id, notification) } // should be shown again + verify(exactly = 0) { arrangement.notificationManager.cancel(tag, id) } } @Test @@ -68,88 +95,217 @@ class CallNotificationManagerTest { runTest(dispatcherProvider.main()) { // given val notification = mockk() - val callNotificationData = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1) + val userName = "user name" + val callNotificationData = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName) + val tag = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val id = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) val (arrangement, callNotificationManager) = Arrangement() .withIncomingNotificationForUserAndCall(notification, callNotificationData) .arrange() arrangement.clearRecordedCallsForNotificationManager() // clear first empty list recorded call - callNotificationManager.handleIncomingCallNotifications(listOf(TEST_CALL1), TEST_USER_ID1) + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName) advanceUntilIdle() // then - verify(exactly = 1) { arrangement.notificationManager.notify(any(), notification) } - verify(exactly = 0) { arrangement.notificationManager.cancel(any()) } + verify(exactly = 1) { arrangement.notificationManager.notify(tag, id, notification) } } @Test - fun `given incoming calls for two users, then show notification for the first call`() = + fun `given an incoming call for one user, when call is updated, then update notification for that call`() = + runTest(dispatcherProvider.main()) { + // given + val notification = mockk() + val userName = "user name" + val callNotificationData = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName) + val updatedCall = TEST_CALL1.copy(conversationName = "new name") + val updatedCallNotificationData = provideCallNotificationData(TEST_USER_ID1, updatedCall, userName) + val tag = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val id = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val (arrangement, callNotificationManager) = Arrangement() + .withIncomingNotificationForUserAndCall(notification, callNotificationData) + .withIncomingNotificationForUserAndCall(notification, updatedCallNotificationData) + .arrange() + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName) + advanceUntilIdle() + arrangement.clearRecordedCallsForNotificationManager() // clear first empty list recorded call + // when + callNotificationManager.handleIncomingCalls(listOf(updatedCall), TEST_USER_ID1, userName) // updated call + advanceUntilIdle() + // then + verify(exactly = 1) { arrangement.notificationManager.notify(tag, id, notification) } // should be updated + } + + @Test + fun `given an incoming call for one user, when call is not updated, then do not update notification for that call`() = + runTest(dispatcherProvider.main()) { + // given + val notification = mockk() + val userName = "user name" + val callNotificationData = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName) + val tag = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val id = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val (arrangement, callNotificationManager) = Arrangement() + .withIncomingNotificationForUserAndCall(notification, callNotificationData) + .arrange() + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName) + advanceUntilIdle() + arrangement.clearRecordedCallsForNotificationManager() // clear first empty list recorded call + // when + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName) // same call + advanceUntilIdle() + // then + verify(exactly = 0) { arrangement.notificationManager.notify(tag, id, notification) } // should not be updated + } + + @Test + fun `given an incoming call for one same user, when another incoming call appears, then add notification only for this new call`() = + runTest(dispatcherProvider.main()) { + // given + val notification1 = mockk() + val notification2 = mockk() + val userName1 = "user name 1" + val callNotificationData1 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName1) + val callNotificationData2 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL2, userName1) + val tag1 = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val tag2 = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val id1 = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val id2 = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL2.conversationId.toString()) + val (arrangement, callNotificationManager) = Arrangement() + .withIncomingNotificationForUserAndCall(notification1, callNotificationData1) + .withIncomingNotificationForUserAndCall(notification2, callNotificationData2) + .arrange() + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName1) + advanceUntilIdle() + arrangement.clearRecordedCallsForNotificationManager() // clear first empty list recorded call + // when + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1, TEST_CALL2), TEST_USER_ID1, userName1) + advanceUntilIdle() + // then + verify(exactly = 0) { arrangement.notificationManager.notify(tag1, id1, notification1) } // already shown previously + verify(exactly = 1) { arrangement.notificationManager.notify(tag2, id2, notification2) } // should be added now + } + + @Test + fun `given incoming calls for two users, then show notification for the both calls`() = + runTest(dispatcherProvider.main()) { + // given + val notification1 = mockk() + val notification2 = mockk() + val userName1 = "user name 1" + val userName2 = "user name 2" + val callNotificationData1 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName1) + val callNotificationData2 = provideCallNotificationData(TEST_USER_ID2, TEST_CALL2, userName2) + val tag1 = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val tag2 = NotificationConstants.getIncomingCallTag(TEST_USER_ID2.toString()) + val id1 = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val id2 = NotificationConstants.getIncomingCallId(TEST_USER_ID2.toString(), TEST_CALL2.conversationId.toString()) + val (arrangement, callNotificationManager) = Arrangement() + .withIncomingNotificationForUserAndCall(notification1, callNotificationData1) + .withIncomingNotificationForUserAndCall(notification2, callNotificationData2) + .arrange() + arrangement.clearRecordedCallsForNotificationManager() // clear first empty list recorded call + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName1) + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL2), TEST_USER_ID2, userName2) + advanceUntilIdle() + // then + verify(exactly = 1) { arrangement.notificationManager.notify(tag1, id1, notification1) } + verify(exactly = 1) { arrangement.notificationManager.notify(tag2, id2, notification2) } + verify(exactly = 0) { arrangement.notificationManager.cancel(any(), any()) } + } + + @Test + fun `given two incoming calls for the same user, then show notification for the both calls`() = runTest(dispatcherProvider.main()) { // given val notification1 = mockk() val notification2 = mockk() - val callNotificationData1 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1) - val callNotificationData2 = provideCallNotificationData(TEST_USER_ID2, TEST_CALL2) + val userName1 = "user name 1" + val callNotificationData1 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName1) + val callNotificationData2 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL2, userName1) + val tag1 = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val tag2 = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val id1 = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val id2 = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL2.conversationId.toString()) val (arrangement, callNotificationManager) = Arrangement() .withIncomingNotificationForUserAndCall(notification1, callNotificationData1) .withIncomingNotificationForUserAndCall(notification2, callNotificationData2) .arrange() arrangement.clearRecordedCallsForNotificationManager() // clear first empty list recorded call - callNotificationManager.handleIncomingCallNotifications(listOf(TEST_CALL1), TEST_USER_ID1) - callNotificationManager.handleIncomingCallNotifications(listOf(TEST_CALL2), TEST_USER_ID2) + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1, TEST_CALL2), TEST_USER_ID1, userName1) advanceUntilIdle() // then - verify(exactly = 1) { arrangement.notificationManager.notify(any(), notification1) } - verify(exactly = 0) { arrangement.notificationManager.notify(any(), notification2) } + verify(exactly = 1) { arrangement.notificationManager.notify(tag1, id1, notification1) } + verify(exactly = 1) { arrangement.notificationManager.notify(tag2, id2, notification2) } verify(exactly = 0) { arrangement.notificationManager.cancel(any()) } } @Test - fun `given incoming calls for two users, when one call ends, then show notification for another call`() = + fun `given incoming calls for two users, when one call ends, then do not cancel notification for another call`() = runTest(dispatcherProvider.main()) { // given val notification1 = mockk() val notification2 = mockk() - val callNotificationData1 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1) - val callNotificationData2 = provideCallNotificationData(TEST_USER_ID2, TEST_CALL2) + val userName1 = "user name 1" + val userName2 = "user name 2" + val callNotificationData1 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName1) + val callNotificationData2 = provideCallNotificationData(TEST_USER_ID2, TEST_CALL2, userName2) + val tag1 = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val tag2 = NotificationConstants.getIncomingCallTag(TEST_USER_ID2.toString()) + val id1 = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val id2 = NotificationConstants.getIncomingCallId(TEST_USER_ID2.toString(), TEST_CALL2.conversationId.toString()) val (arrangement, callNotificationManager) = Arrangement() .withIncomingNotificationForUserAndCall(notification1, callNotificationData1) .withIncomingNotificationForUserAndCall(notification2, callNotificationData2) .arrange() - callNotificationManager.handleIncomingCallNotifications(listOf(TEST_CALL1), TEST_USER_ID1) - callNotificationManager.handleIncomingCallNotifications(listOf(TEST_CALL2), TEST_USER_ID2) + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName1) + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL2), TEST_USER_ID2, userName2) + arrangement.withActiveNotifications(listOf(mockStatusBarNotification(id1, tag1), mockStatusBarNotification(id2, tag2))) advanceUntilIdle() arrangement.clearRecordedCallsForNotificationManager() // clear calls recorded when initializing the state // when - callNotificationManager.handleIncomingCallNotifications(listOf(), TEST_USER_ID1) + callNotificationManager.handleIncomingCalls(listOf(), TEST_USER_ID1, userName1) // first call is ended advanceUntilIdle() // then - verify(exactly = 0) { arrangement.notificationManager.notify(any(), notification1) } - verify(exactly = 1) { arrangement.notificationManager.notify(any(), notification2) } - verify(exactly = 0) { arrangement.notificationManager.cancel(any()) } + verify(exactly = 1) { arrangement.notificationManager.cancel(tag1, id1) } + verify(exactly = 0) { arrangement.notificationManager.cancel(tag2, id2) } } @Test - fun `given incoming calls for two users, when both call ends, then hide notification`() = + fun `given incoming calls for two users, when both call ends, then hide all notifications`() = runTest(dispatcherProvider.main()) { // given val notification1 = mockk() val notification2 = mockk() - val callNotificationData1 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1) - val callNotificationData2 = provideCallNotificationData(TEST_USER_ID2, TEST_CALL2) + val userName1 = "user name 1" + val userName2 = "user name 2" + val callNotificationData1 = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName1) + val callNotificationData2 = provideCallNotificationData(TEST_USER_ID2, TEST_CALL2, userName2) + val tag1 = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val tag2 = NotificationConstants.getIncomingCallTag(TEST_USER_ID2.toString()) + val id1 = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) + val id2 = NotificationConstants.getIncomingCallId(TEST_USER_ID2.toString(), TEST_CALL2.conversationId.toString()) val (arrangement, callNotificationManager) = Arrangement() .withIncomingNotificationForUserAndCall(notification1, callNotificationData1) .withIncomingNotificationForUserAndCall(notification2, callNotificationData2) .arrange() - callNotificationManager.handleIncomingCallNotifications(listOf(TEST_CALL1), TEST_USER_ID1) - callNotificationManager.handleIncomingCallNotifications(listOf(TEST_CALL2), TEST_USER_ID2) + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName1) + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL2), TEST_USER_ID2, userName2) advanceUntilIdle() arrangement.clearRecordedCallsForNotificationManager() // clear calls recorded when initializing the state + arrangement.withActiveNotifications( + listOf( + mockStatusBarNotification(id1, tag1), + mockStatusBarNotification(id2, tag2) + ) + ) + // when - callNotificationManager.handleIncomingCallNotifications(listOf(), TEST_USER_ID1) - callNotificationManager.handleIncomingCallNotifications(listOf(), TEST_USER_ID2) + callNotificationManager.handleIncomingCalls(listOf(), TEST_USER_ID1, userName1) + callNotificationManager.handleIncomingCalls(listOf(), TEST_USER_ID2, userName2) // then - verify(exactly = 0) { arrangement.notificationManager.notify(any(), notification1) } - verify(exactly = 0) { arrangement.notificationManager.notify(any(), notification2) } - verify(exactly = 1) { arrangement.notificationManager.cancel(any()) } + verify(exactly = 0) { arrangement.notificationManager.notify(tag1, id1, notification1) } + verify(exactly = 0) { arrangement.notificationManager.notify(tag2, id2, notification2) } + verify(exactly = 1) { arrangement.notificationManager.cancel(tag1, id1) } + verify(exactly = 1) { arrangement.notificationManager.cancel(tag2, id2) } } @Test @@ -157,36 +313,42 @@ class CallNotificationManagerTest { runTest(dispatcherProvider.main()) { // given val notification = mockk() - val callNotificationData = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1) + val userName = "user name" + val callNotificationData = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName) + val tag = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val id = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) val (arrangement, callNotificationManager) = Arrangement() .withIncomingNotificationForUserAndCall(notification, callNotificationData) .arrange() // when - callNotificationManager.handleIncomingCallNotifications(listOf(TEST_CALL1), TEST_USER_ID1) + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName) advanceTimeBy((DEBOUNCE_TIME - 50).milliseconds) - callNotificationManager.handleIncomingCallNotifications(listOf(), TEST_USER_ID1) + callNotificationManager.handleIncomingCalls(listOf(), TEST_USER_ID1, userName) // then - verify(exactly = 0) { arrangement.notificationManager.notify(any(), notification) } - verify(exactly = 1) { arrangement.notificationManager.cancel(NotificationIds.CALL_INCOMING_NOTIFICATION_ID.ordinal) } + verify(exactly = 0) { arrangement.notificationManager.notify(tag, id, notification) } } @Test fun `given incoming call, when end call comes some time after start, then first show notification and then hide`() = runTest(dispatcherProvider.main()) { // given + val userName = "user name" + val callNotificationData = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1, userName) + val tag = NotificationConstants.getIncomingCallTag(TEST_USER_ID1.toString()) + val id = NotificationConstants.getIncomingCallId(TEST_USER_ID1.toString(), TEST_CALL1.conversationId.toString()) val notification = mockk() - val callNotificationData = provideCallNotificationData(TEST_USER_ID1, TEST_CALL1) val (arrangement, callNotificationManager) = Arrangement() .withIncomingNotificationForUserAndCall(notification, callNotificationData) .arrange() arrangement.clearRecordedCallsForNotificationManager() // clear first empty list recorded call // when - callNotificationManager.handleIncomingCallNotifications(listOf(TEST_CALL1), TEST_USER_ID1) + callNotificationManager.handleIncomingCalls(listOf(TEST_CALL1), TEST_USER_ID1, userName) advanceTimeBy((DEBOUNCE_TIME + 50).milliseconds) - callNotificationManager.handleIncomingCallNotifications(listOf(), TEST_USER_ID1) + arrangement.withActiveNotifications(listOf(mockStatusBarNotification(id, tag))) + callNotificationManager.handleIncomingCalls(listOf(), TEST_USER_ID1, userName) // then - verify(exactly = 1) { arrangement.notificationManager.notify(any(), notification) } - verify(exactly = 1) { arrangement.notificationManager.cancel(any()) } + verify(exactly = 1) { arrangement.notificationManager.notify(tag, id, notification) } + verify(exactly = 1) { arrangement.notificationManager.cancel(tag, id) } } private inner class Arrangement { @@ -200,13 +362,11 @@ class CallNotificationManagerTest { @MockK lateinit var callNotificationBuilder: CallNotificationBuilder - private var callNotificationManager: CallNotificationManager - init { MockKAnnotations.init(this, relaxUnitFun = true) mockkStatic(NotificationManagerCompat::from) every { NotificationManagerCompat.from(any()) } returns notificationManager - callNotificationManager = CallNotificationManager(context, dispatcherProvider, callNotificationBuilder) + withActiveNotifications(emptyList()) } fun clearRecordedCallsForNotificationManager() { @@ -223,11 +383,12 @@ class CallNotificationManagerTest { fun withIncomingNotificationForUserAndCall(notification: Notification, forCallNotificationData: CallNotificationData) = apply { every { callNotificationBuilder.getIncomingCallNotification(eq(forCallNotificationData)) } returns notification } - fun withOutgoingNotificationForUserAndCall(notification: Notification, forCallNotificationData: CallNotificationData) = apply { - every { callNotificationBuilder.getOutgoingCallNotification(eq(forCallNotificationData)) } returns notification + + fun withActiveNotifications(list: List) = apply { + every { notificationManager.activeNotifications } returns list } - fun arrange() = this to callNotificationManager + fun arrange() = this to CallNotificationManager(context, dispatcherProvider, callNotificationBuilder) } companion object { @@ -256,8 +417,9 @@ class CallNotificationManagerTest { callerTeamName = "team_1" ) - private fun provideCallNotificationData(userId: UserId, call: Call) = CallNotificationData( + private fun provideCallNotificationData(userId: UserId, call: Call, userName: String) = CallNotificationData( userId = userId, + userName = userName, conversationId = call.conversationId, conversationName = call.conversationName, conversationType = call.conversationType, @@ -265,5 +427,12 @@ class CallNotificationManagerTest { callerTeamName = call.callerTeamName, callStatus = call.status ) + + fun mockStatusBarNotification(id: Int, tag: String): StatusBarNotification { + val statusBarNotification = mockk() + every { statusBarNotification.id } returns id + every { statusBarNotification.tag } returns tag + return statusBarNotification + } } } diff --git a/app/src/test/kotlin/com/wire/android/notification/WireNotificationManagerTest.kt b/app/src/test/kotlin/com/wire/android/notification/WireNotificationManagerTest.kt index 06964715d57..cf0aa3a376e 100644 --- a/app/src/test/kotlin/com/wire/android/notification/WireNotificationManagerTest.kt +++ b/app/src/test/kotlin/com/wire/android/notification/WireNotificationManagerTest.kt @@ -113,7 +113,7 @@ class WireNotificationManagerTest { userName = TestUser.SELF_USER.handle!! ) } - verify(exactly = 0) { arrangement.callNotificationManager.handleIncomingCallNotifications(any(), any()) } + verify(exactly = 0) { arrangement.callNotificationManager.handleIncomingCalls(any(), any(), any()) } } // todo: check later with boris! @@ -142,47 +142,47 @@ class WireNotificationManagerTest { advanceUntilIdle() verify(exactly = 0) { arrangement.coreLogic.getSessionScope(any()) } - verify(exactly = 1) { arrangement.callNotificationManager.hideAllNotifications() } + verify(exactly = 1) { arrangement.callNotificationManager.hideAllIncomingCallNotifications() } } @Test fun givenSomeIncomingCall_whenObserving_thenCallHandleIncomingCallNotifications() = runTestWithCancellation(dispatcherProvider.main()) { - val userId = provideUserId("user1") + val user = TestUser.SELF_USER val incomingCalls = listOf(provideCall()) val (arrangement, manager) = Arrangement() - .withSpecificUserSession(userId = userId, incomingCalls = incomingCalls) + .withSpecificUserSession(userId = user.id, incomingCalls = incomingCalls) .withMessageNotifications(listOf()) .withCurrentScreen(CurrentScreen.SomeOther()) - .withCurrentUserSession(CurrentSessionResult.Success(AccountInfo.Valid(userId))) + .withCurrentUserSession(CurrentSessionResult.Success(AccountInfo.Valid(user.id))) .arrange() - manager.observeNotificationsAndCallsWhileRunning(listOf(userId), this) + manager.observeNotificationsAndCallsWhileRunning(listOf(user.id), this) runCurrent() verify(exactly = 1) { - arrangement.callNotificationManager.handleIncomingCallNotifications(incomingCalls, userId) + arrangement.callNotificationManager.handleIncomingCalls(incomingCalls, user.id, user.handle!!) } } @Test fun givenSomeIncomingCall_whenCurrentUserIsDifferentFromCallReceiver_thenCallHandleIncomingCallNotifications() = runTestWithCancellation(dispatcherProvider.main()) { - val user1 = provideUserId("user1") - val user2 = provideUserId("user2") + val user1 = TestUser.SELF_USER.copy(id = provideUserId("user1")) + val user2 = TestUser.SELF_USER.copy(id = provideUserId("user2")) val incomingCalls = listOf(provideCall()) val (arrangement, manager) = Arrangement() - .withSpecificUserSession(userId = user1, incomingCalls = listOf()) - .withSpecificUserSession(userId = user2, incomingCalls = incomingCalls) + .withSpecificUserSession(userId = user1.id, incomingCalls = listOf()) + .withSpecificUserSession(userId = user2.id, incomingCalls = incomingCalls) .withMessageNotifications(listOf()) .withCurrentScreen(CurrentScreen.SomeOther()) - .withCurrentUserSession(CurrentSessionResult.Success(provideAccountInfo(user1.value))) + .withCurrentUserSession(CurrentSessionResult.Success(provideAccountInfo(user1.id.value))) .arrange() - manager.observeNotificationsAndCallsWhileRunning(listOf(user1, user2), this) + manager.observeNotificationsAndCallsWhileRunning(listOf(user1.id, user2.id), this) runCurrent() verify(exactly = 1) { - arrangement.callNotificationManager.handleIncomingCallNotifications(incomingCalls, user2) + arrangement.callNotificationManager.handleIncomingCalls(incomingCalls, user2.id, user2.handle!!) } } @@ -209,7 +209,7 @@ class WireNotificationManagerTest { newNotifications = any(), userId = any(), userName = TestUser.SELF_USER.handle!! ) } - verify(exactly = 1) { arrangement.callNotificationManager.hideAllNotifications() } + verify(exactly = 1) { arrangement.callNotificationManager.hideAllIncomingCallNotifications() } } @Test @@ -236,7 +236,7 @@ class WireNotificationManagerTest { any(), any(), TestUser.SELF_USER.handle!! ) } - verify(exactly = 1) { arrangement.callNotificationManager.hideAllNotifications() } + verify(exactly = 1) { arrangement.callNotificationManager.hideAllIncomingCallNotifications() } } @Test @@ -1131,7 +1131,7 @@ class WireNotificationManagerTest { coEvery { callsScope.getIncomingCalls } returns getIncomingCallsUseCase coEvery { callsScope.establishedCall } returns establishedCall coEvery { callsScope.observeOutgoingCall } returns observeOutgoingCall - coEvery { callNotificationManager.handleIncomingCallNotifications(any(), any()) } returns Unit + coEvery { callNotificationManager.handleIncomingCalls(any(), any(), any()) } returns Unit coEvery { callNotificationManager.builder.getNotificationTitle(any()) } returns "Test title" coEvery { messageScope.getNotifications } returns getNotificationsUseCase coEvery { messageScope.markMessagesAsNotified } returns markMessagesAsNotified diff --git a/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt index 469783fd2e5..634a7f00081 100644 --- a/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt @@ -51,6 +51,7 @@ import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeInstanceOf +import org.amshove.kluent.shouldHaveSize import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -112,9 +113,11 @@ class CommonTopAppBarViewModelTest { val state = commonTopAppBarViewModel.state val info = state.connectivityState - info shouldBeInstanceOf ConnectivityUIState.EstablishedCall::class - info as ConnectivityUIState.EstablishedCall - info.conversationId shouldBeEqualTo ongoingCall.conversationId + info.shouldBeInstanceOf().let { + it.calls.shouldHaveSize(1) + it.calls[0].shouldBeInstanceOf() + .conversationId shouldBeEqualTo ongoingCall.conversationId + } } @Test @@ -132,9 +135,11 @@ class CommonTopAppBarViewModelTest { val state = commonTopAppBarViewModel.state val info = state.connectivityState - info shouldBeInstanceOf ConnectivityUIState.EstablishedCall::class - info as ConnectivityUIState.EstablishedCall - info.isMuted shouldBe true + info.shouldBeInstanceOf().let { + it.calls.shouldHaveSize(1) + it.calls[0].shouldBeInstanceOf() + .isMuted shouldBe true + } } @Test @@ -153,9 +158,11 @@ class CommonTopAppBarViewModelTest { val state = commonTopAppBarViewModel.state val info = state.connectivityState - info shouldBeInstanceOf ConnectivityUIState.EstablishedCall::class - info as ConnectivityUIState.EstablishedCall - info.isMuted shouldBe false + info.shouldBeInstanceOf().let { + it.calls.shouldHaveSize(1) + it.calls[0].shouldBeInstanceOf() + .isMuted shouldBe false + } } @Test @@ -174,7 +181,10 @@ class CommonTopAppBarViewModelTest { val state = commonTopAppBarViewModel.state val info = state.connectivityState - info shouldBeInstanceOf ConnectivityUIState.EstablishedCall::class + info.shouldBeInstanceOf().let { + it.calls.shouldHaveSize(1) + it.calls[0].shouldBeInstanceOf() + } } @Test @@ -192,7 +202,10 @@ class CommonTopAppBarViewModelTest { val state = commonTopAppBarViewModel.state val info = state.connectivityState - info shouldBeInstanceOf ConnectivityUIState.IncomingCall::class + info.shouldBeInstanceOf().let { + it.calls.shouldHaveSize(1) + it.calls[0].shouldBeInstanceOf() + } } @Test @@ -210,7 +223,10 @@ class CommonTopAppBarViewModelTest { val state = commonTopAppBarViewModel.state val info = state.connectivityState - info shouldBeInstanceOf ConnectivityUIState.OutgoingCall::class + info.shouldBeInstanceOf().let { + it.calls.shouldHaveSize(1) + it.calls[0].shouldBeInstanceOf() + } } @Test @@ -226,20 +242,20 @@ class CommonTopAppBarViewModelTest { } @Test - fun givenEstablishedAndIncomingCall_whenActiveCallFlowIsCalled_thenEmitEstablishedCallOnly() = runTest { + fun givenEstablishedAndIncomingCall_whenActiveCallFlowsIsCalled_thenEmitBoth() = runTest { val (_, commonTopAppBarViewModel) = Arrangement() .withCurrentSessionExist() .withOngoingCall(isMuted = true) .withIncomingCall() - .withOutgoingCall() + .withoutOutgoingCall() .withCurrentScreen(CurrentScreen.Home) .withSyncState(SyncState.Waiting) .arrange() - val flow = commonTopAppBarViewModel.activeCallFlow(userId) + val flow = commonTopAppBarViewModel.activeCallsFlow(userId) flow.collect { - it shouldBeEqualTo ongoingCall + it shouldBeEqualTo listOf(ongoingCall, incomingCall) } }