diff --git a/app/src/main/java/com/mango/moshen/ui/im/InputPanel.java b/app/src/main/java/com/mango/moshen/ui/im/InputPanel.java index 201bc642e..cc35dd31a 100644 --- a/app/src/main/java/com/mango/moshen/ui/im/InputPanel.java +++ b/app/src/main/java/com/mango/moshen/ui/im/InputPanel.java @@ -20,6 +20,7 @@ import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; +import android.widget.CheckBox; import android.widget.Chronometer; import android.widget.EditText; import android.widget.FrameLayout; @@ -27,7 +28,11 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.alibaba.fastjson.JSONObject; +import com.chad.library.adapter.base.BaseQuickAdapter; import com.effective.android.panel.PanelSwitchHelper; import com.effective.android.panel.interfaces.PanelHeightMeasurer; import com.effective.android.panel.interfaces.listener.OnPanelChangeListener; @@ -35,6 +40,8 @@ import com.effective.android.panel.view.panel.IPanelView; import com.effective.android.panel.view.panel.PanelView; import com.mango.moshen.R; import com.mango.moshen.ui.im.actions.GiftAction; +import com.mango.moshen.ui.im.adpter.PhotoPreviewAdapter; +import com.mango.xchat_android_library.utils.ListUtils; import com.mango.xchat_android_library.utils.SingleToastUtil; import com.mango.xchat_android_library.utils.SizeUtils; import com.netease.nim.uikit.api.NimUIKit; @@ -48,10 +55,14 @@ import com.netease.nim.uikit.business.session.emoji.EmoticonPickerView; import com.netease.nim.uikit.business.session.emoji.IEmoticonSelectedListener; import com.netease.nim.uikit.business.session.emoji.MoonUtil; import com.netease.nim.uikit.business.session.event.ActiveEvent; +import com.netease.nim.uikit.business.session.helper.SendImageHelper; import com.netease.nim.uikit.business.session.module.Container; import com.netease.nim.uikit.business.session.module.input.ActionsPanel; import com.netease.nim.uikit.business.session.module.input.NimAudioChatEvent; import com.netease.nim.uikit.common.antispam.AntiSpamEvent; +import com.netease.nim.uikit.common.media.picker.PickImageHelper; +import com.netease.nim.uikit.common.media.picker.model.AlbumInfo; +import com.netease.nim.uikit.common.media.picker.model.PhotoInfo; import com.netease.nim.uikit.common.ui.dialog.EasyAlertDialogHelper; import com.netease.nim.uikit.common.util.AntiSpamUtil; import com.netease.nim.uikit.common.util.log.LogUtil; @@ -73,8 +84,17 @@ import com.netease.nimlib.sdk.msg.model.IMMessage; import org.greenrobot.eventbus.EventBus; import java.io.File; +import java.util.ArrayList; import java.util.List; +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.Scheduler; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function; +import io.reactivex.schedulers.Schedulers; + /** * 底部文本编辑,语音等模块 * Created by hzxuwen on 2015/6/16. @@ -84,11 +104,10 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba private static final String TAG = "MsgSendLayout"; private static final int SHOW_LAYOUT_DELAY = 0; - + private final List selectPhotos = new ArrayList<>(); protected Container container; protected View view; protected Handler uiHandler; - protected EditText messageEditText;// 文本消息编辑框 protected Button audioRecordBtn; // 录音按钮 protected View audioAnimLayout; // 录音动画布局 @@ -96,7 +115,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba protected View switchToTextButtonInInputBar;// 文本消息选择按钮 protected View switchToAudioButtonInInputBar;// 语音消息选择按钮 protected View sendMessageButtonInInputBar;// 发送消息按钮 - protected View emojiButtonInInputBar;// 发送消息按钮 + protected ImageView emojiButtonInInputBar;// 发送消息按钮 protected View messageInputBar; // 表情 protected EmoticonPickerView emoticonPickerView; // 贴图表情控件 @@ -110,14 +129,13 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba private boolean started = false; private boolean cancelled = false; private boolean touched = false; // 是否按着 - private boolean isKeyboardShowed = true; // 是否显示键盘 + private boolean isKeyboardShowed = false; // 是否显示键盘 private boolean isTextAudioSwitchShow; // 是否展示左侧语音按钮 // adapter private List actions; // data private long typingTime = 0; private TextWatcher aitTextWatcher; - private volatile boolean disable; private boolean isChat; private PanelSwitchHelper mHelper; private final Runnable showTextRunnable = new Runnable() { @@ -129,6 +147,12 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba private ImageView ivImage; private ImageView ivCamera; private ImageView ivGift; + private CheckBox cbOrigin; + private TextView tvSendImage; + private RecyclerView rvPreviewPhotos; + private PhotoPreviewAdapter photoPreviewAdapter; + private int previewPhotoNum; + private TextView tvUserPhoto; /** * ************************* 键盘布局切换 ******************************* */ @@ -152,7 +176,6 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba } mHelper.resetState(); } else if (v == ivGift) { - ivGift.postDelayed(() -> { for (BaseAction action : actions) { if (action instanceof GiftAction) { @@ -162,7 +185,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba } }, !mHelper.isResetState() ? 300 : 0); mHelper.resetState(); - } else if (v == ivImage) { + } else if (v == tvUserPhoto) { for (BaseAction action : actions) { if (action instanceof PhotoAction) { action.onClick(); @@ -170,6 +193,29 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba } } mHelper.resetState(); + } else if (v == tvSendImage) { + if (!isChat) { + SingleToastUtil.showToast("暂未达到可发起私聊等级"); + return; + } + if (photoPreviewAdapter != null) { + for (BaseAction action : actions) { + if (action instanceof PhotoAction) { + SendImageHelper.sendPhotos(container.activity, (PhotoAction) action, selectPhotos, cbOrigin.isChecked()); + selectPhotos.clear(); + for (PhotoInfo info : photoPreviewAdapter.getData()) { + info.setIndex(0); + info.setChoose(false); + } + previewPhotoNum = 0; + tvSendImage.setEnabled(false); + photoPreviewAdapter.notifyDataSetChanged(); + break; + } + } + mHelper.resetState(); + } + } } }; @@ -192,8 +238,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba int[] location = new int[2]; view.getLocationOnScreen(location); - return event.getRawX() < location[0] || event.getRawX() > location[0] + view.getWidth() - || event.getRawY() < location[1] - 40; + return event.getRawX() < location[0] || event.getRawX() > location[0] + view.getWidth() || event.getRawY() < location[1] - 40; } public void onPause() { @@ -211,7 +256,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba } public boolean collapse(boolean immediately) { - boolean respond = !mHelper.isResetState(); + boolean respond = mHelper.isPanelState() || mHelper.isKeyboardState() || isKeyboardShowed; mHelper.resetState(); return respond; } @@ -225,61 +270,56 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba initInputBarListener(); initTextEdit(); initAudioRecordButton(); + initPanelSwitchHelper(); restoreText(false); for (int i = 0; i < actions.size(); ++i) { actions.get(i).setIndex(i); actions.get(i).setContainer(container); } + } - if (disable) { - disableButtons(); - } + private void initPanelSwitchHelper() { if (mHelper == null) { mHelper = new PanelSwitchHelper.Builder(container.activity) - //可选 - .addKeyboardStateListener((visible, height) -> { - Log.d(TAG, "系统键盘是否可见 : " + visible + " 高度为:" + height); - }) - //可选 - .addEditTextFocusChangeListener((view, hasFocus) -> { - Log.d(TAG, "输入框是否获得焦点 : " + hasFocus); - }) - //可选 .addViewClickListener(view -> { if (view == null) return; if (view.getId() == R.id.emoji_button) { switchToTextLayout(false); } - Log.d(TAG, "点击了View : " + view); }) - //可选 .addPanelChangeListener(new OnPanelChangeListener() { @Override public void onKeyboard() { - Log.d(TAG, "唤起系统输入法"); + isKeyboardShowed = true; container.proxy.onInputPanelExpand(); + emojiButtonInInputBar.setImageResource(R.drawable.nim_message_input_emotion_pressed); } @Override public void onNone() { - Log.d(TAG, "隐藏所有面板"); - + isKeyboardShowed = false; + emojiButtonInInputBar.setImageResource(R.drawable.nim_message_input_emotion_pressed); } @Override public void onPanel(IPanelView view) { - Log.d(TAG, "唤起面板 : " + view); container.proxy.onInputPanelExpand(); + if (view.getBindingTriggerViewId() == R.id.emoji_button) { + emojiButtonInInputBar.setImageResource(R.drawable.nim_message_input_keyboard); + emoticonPickerView.show(InputPanel.this); + } else if (view.getBindingTriggerViewId() == R.id.iv_image) { + emojiButtonInInputBar.setImageResource(R.drawable.nim_message_input_emotion_pressed); + initPreviewPhotos(); + } } @Override public void onPanelSizeChange(IPanelView panelView, boolean portrait, int oldWidth, int oldHeight, int width, int height) { } - }) - .addPanelHeightMeasurer(new PanelHeightMeasurer() { + }).addPanelHeightMeasurer(new PanelHeightMeasurer() { @Override public boolean synchronizeKeyboardHeight() { return false; @@ -294,8 +334,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba public int getPanelTriggerId() { return R.id.iv_image; } - }) - .addPanelHeightMeasurer(new PanelHeightMeasurer() { + }).addPanelHeightMeasurer(new PanelHeightMeasurer() { @Override public boolean synchronizeKeyboardHeight() { return false; @@ -310,8 +349,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba public int getPanelTriggerId() { return R.id.emoji_button; } - }) - .logTrack(true)//output log + }).logTrack(false)//output log .build(); messageListView.setPanelSwitchHelper(mHelper); @@ -319,6 +357,61 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba } + @SuppressLint("CheckResult") + private void initPreviewPhotos() { + if (photoPreviewAdapter != null) { + photoPreviewAdapter.notifyDataSetChanged(); + return; + } + rvPreviewPhotos.setAdapter(photoPreviewAdapter = new PhotoPreviewAdapter()); + rvPreviewPhotos.setItemAnimator(null); + rvPreviewPhotos.setLayoutManager(new LinearLayoutManager(container.activity, LinearLayoutManager.HORIZONTAL, false)); + photoPreviewAdapter.setOnItemClickListener((adapter, view, position) -> { + PhotoInfo photoInfo = photoPreviewAdapter.getItem(position); + if (photoInfo != null) { + if (photoInfo.isChoose()) { + photoInfo.setChoose(false); + int tempIndex = photoInfo.getIndex(); + for (PhotoInfo info : photoPreviewAdapter.getData()) { + if (info.getIndex() > tempIndex) { + info.setIndex(info.getIndex() - 1); + } + } + photoInfo.setIndex(0); + previewPhotoNum--; + selectPhotos.remove(tempIndex - 1); + photoPreviewAdapter.notifyDataSetChanged(); + } else { + if (previewPhotoNum >= 9) { + SingleToastUtil.showToast("一次最多发送9张图片!"); + return; + } + previewPhotoNum++; + photoInfo.setChoose(true); + photoInfo.setIndex(previewPhotoNum); + selectPhotos.add(photoInfo); + photoPreviewAdapter.notifyItemChanged(position); + } + tvSendImage.setEnabled(previewPhotoNum > 0); + } + }); + + Observable.just(PickImageHelper.getAllMediaPhotos(container.activity, 40)) + .subscribeOn(Schedulers.io()) + .flatMap(albumInfos -> { + List photoInfos = new ArrayList<>(); + for (AlbumInfo albumInfo : albumInfos) { + if (!ListUtils.isListEmpty(albumInfo.getList())) { + photoInfos.addAll(albumInfo.getList()); + } + } + return Observable.just(photoInfos); + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(photoInfos -> photoPreviewAdapter.setNewData(photoInfos)); + } + + public void setCustomization(SessionCustomization customization) { this.customization = customization; if (customization != null) { @@ -331,6 +424,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba setCustomization(customization); } + @SuppressLint("CheckResult") private void initViews() { messageInputBar = view.findViewById(R.id.textMessageLayout); switchToTextButtonInInputBar = view.findViewById(R.id.buttonTextMessage); @@ -348,8 +442,6 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba // 表情 emoticonPickerView = view.findViewById(R.id.emoticon_picker_view); - emoticonPickerView.show(this); - // 显示录音按钮 switchToTextButtonInInputBar.setVisibility(View.GONE); switchToAudioButtonInInputBar.setVisibility(View.VISIBLE); @@ -369,27 +461,13 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba ivCamera.setOnClickListener(clickListener); ivGift.setOnClickListener(clickListener); - } - - public void disableButtons() { - if (messageEditText != null && audioRecordBtn != null && - switchToTextButtonInInputBar != null && - switchToAudioButtonInInputBar != null && - emojiButtonInInputBar != null) { - messageEditText.setEnabled(false); - messageEditText.setHint("禁言中,快找管理员解除禁言吧!"); - messageEditText.setHintTextColor(Color.parseColor("#999999")); - audioRecordBtn.setEnabled(false); - switchToTextButtonInInputBar.setEnabled(false); - switchToAudioButtonInInputBar.setEnabled(false); - emojiButtonInInputBar.setEnabled(false); - } else { - setDisable(true); - } - } - - public void setDisable(boolean disable) { - this.disable = disable; + rvPreviewPhotos = view.findViewById(R.id.rv_preview_photos); + tvSendImage = view.findViewById(R.id.tv_send_image); + tvSendImage.setEnabled(false); + tvUserPhoto = view.findViewById(R.id.tv_user_photo); + cbOrigin = view.findViewById(R.id.cb_origin); + tvSendImage.setOnClickListener(clickListener); + tvUserPhoto.setOnClickListener(clickListener); } private void initInputBarListener() { @@ -405,9 +483,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba //当actionId == XX_SEND 或者 XX_DONE时都触发 //或者event.getKeyCode == ENTER 且 event.getAction == ACTION_DOWN时也触发 //注意,这是一定要判断event != null。因为在某些输入法上会返回null。 - if (actionId == EditorInfo.IME_ACTION_SEND - || actionId == EditorInfo.IME_ACTION_DONE - || (event != null && KeyEvent.KEYCODE_ENTER == event.getKeyCode() && KeyEvent.ACTION_DOWN == event.getAction())) { + if (actionId == EditorInfo.IME_ACTION_SEND || actionId == EditorInfo.IME_ACTION_DONE || (event != null && KeyEvent.KEYCODE_ENTER == event.getKeyCode() && KeyEvent.ACTION_DOWN == event.getAction())) { //处理事件 onTextMessageSendButtonPressed(); return true; @@ -540,7 +616,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba // 切换成音频,收起键盘,按钮切换成键盘 private void switchToAudioLayout() { if (!isChat) { - SingleToastUtil.showToast("等级不够"); + SingleToastUtil.showToast("暂未达到可发起私聊等级"); return; } Log.e(TAG, "switchToAudioLayout: "); @@ -559,10 +635,8 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba // 隐藏键盘布局 public void hideInputMethod() { - isKeyboardShowed = false; uiHandler.removeCallbacks(showTextRunnable); - InputMethodManager imm = (InputMethodManager) container.activity.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(messageEditText.getWindowToken(), 0); + mHelper.resetState(); messageEditText.clearFocus(); } @@ -572,9 +646,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba //如果已经显示,则继续操作时不需要把光标定位到最后 if (!isKeyboardShowed) { editTextMessage.setSelection(editTextMessage.getText().length()); - isKeyboardShowed = true; } - mHelper.toKeyboardState(); container.proxy.onInputPanelExpand(); } @@ -594,7 +666,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba private void checkSendButtonEnable(EditText editText) { String textMessage = editText.getText().toString(); setEditTextState(); - if (!TextUtils.isEmpty(StringUtil.removeBlanks(textMessage)) && editText.hasFocus()) { + if (!TextUtils.isEmpty(StringUtil.removeBlanks(textMessage))) { sendMessageButtonInInputBar.setVisibility(View.VISIBLE); } else { sendMessageButtonInInputBar.setVisibility(View.GONE); @@ -631,8 +703,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba @Override public void onTextAdd(String content, int start, int length) { - if (messageEditText.getVisibility() != View.VISIBLE || - (emoticonPickerView != null && emoticonPickerView.getVisibility() == View.VISIBLE)) { + if (messageEditText.getVisibility() != View.VISIBLE || (emoticonPickerView != null && emoticonPickerView.getVisibility() == View.VISIBLE)) { switchToTextLayout(true); } else { uiHandler.postDelayed(showTextRunnable, SHOW_LAYOUT_DELAY); @@ -666,8 +737,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba touched = true; initAudioRecord(); onStartAudioRecord(); - } else if (event.getAction() == MotionEvent.ACTION_CANCEL - || event.getAction() == MotionEvent.ACTION_UP) { + } else if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) { touched = false; onEndAudioRecord(isCancelled(v, event)); } else if (event.getAction() == MotionEvent.ACTION_MOVE) { @@ -693,8 +763,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba * 开始语音录制 */ private void onStartAudioRecord() { - container.activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + container.activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); audioMessageHelper.startRecord(); cancelled = false; } @@ -845,15 +914,6 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba } } - public void reloadActions(List actions) { - this.actions = actions; - for (int i = 0; i < this.actions.size(); ++i) { - this.actions.get(i).setIndex(i); - this.actions.get(i).setContainer(container); - } - ActionsPanel.init(view, actions); - } - public void setLimitLevel(boolean isChat, String msg) { this.isChat = isChat; setEditTextState(); @@ -866,12 +926,22 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba messageEditText.setFocusableInTouchMode(false); messageEditText.setEnabled(false); sendMessageButtonInInputBar.setEnabled(false); + emojiButtonInInputBar.setEnabled(false); + switchToAudioButtonInInputBar.setEnabled(false); + ivImage.setEnabled(false); + ivCamera.setEnabled(false); + ivGift.setEnabled(false); } else { messageEditText.setHint("请输入消息"); messageEditText.setFocusable(true); messageEditText.setFocusableInTouchMode(true); messageEditText.setEnabled(true); sendMessageButtonInInputBar.setEnabled(true); + emojiButtonInInputBar.setEnabled(true); + switchToAudioButtonInInputBar.setEnabled(true); + ivImage.setEnabled(true); + ivCamera.setEnabled(true); + ivGift.setEnabled(true); } } diff --git a/app/src/main/java/com/mango/moshen/ui/im/adpter/PhotoPreviewAdapter.kt b/app/src/main/java/com/mango/moshen/ui/im/adpter/PhotoPreviewAdapter.kt new file mode 100644 index 000000000..92c50871a --- /dev/null +++ b/app/src/main/java/com/mango/moshen/ui/im/adpter/PhotoPreviewAdapter.kt @@ -0,0 +1,29 @@ +package com.mango.moshen.ui.im.adpter + +import android.widget.ImageView +import android.widget.TextView +import com.bumptech.glide.signature.ObjectKey +import com.chad.library.adapter.base.BaseQuickAdapter +import com.chad.library.adapter.base.BaseViewHolder +import com.mango.core.utils.LogUtils +import com.mango.moshen.R +import com.mango.moshen.ui.utils.ImageLoadUtilsV2 +import com.netease.nim.uikit.common.media.picker.model.PhotoInfo +import com.netease.nim.uikit.support.glide.GlideApp + +class PhotoPreviewAdapter : + BaseQuickAdapter(R.layout.item_message_photo) { + + override fun convert(helper: BaseViewHolder, item: PhotoInfo) { + val imageView = helper.getView(R.id.iv_item_photo) + GlideApp.with(imageView) + .load(item.filePath) + .placeholder(R.drawable.default_cover) + .signature(ObjectKey(item)) + .into(imageView) + val textView = helper.getView(R.id.tv_item_number) + textView.isSelected = item.isChoose + textView.text = if (item.isChoose) "${item.index}" else "" + + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_circle_photo_preview_bg.xml b/app/src/main/res/drawable/shape_circle_photo_preview_bg.xml new file mode 100644 index 000000000..2a72ad946 --- /dev/null +++ b/app/src/main/res/drawable/shape_circle_photo_preview_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_photo.xml b/app/src/main/res/layout/item_message_photo.xml new file mode 100644 index 000000000..7ba274113 --- /dev/null +++ b/app/src/main/res/layout/item_message_photo.xml @@ -0,0 +1,34 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/module_public_chat_hall/res/layout/nim_message_photo_layout.xml b/app/src/module_public_chat_hall/res/layout/nim_message_photo_layout.xml index e7e106925..88ac6818b 100644 --- a/app/src/module_public_chat_hall/res/layout/nim_message_photo_layout.xml +++ b/app/src/module_public_chat_hall/res/layout/nim_message_photo_layout.xml @@ -1,21 +1,60 @@ + android:orientation="vertical"> - + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/module_public_chat_hall/res/layout/nim_msg_fragment.xml b/app/src/module_public_chat_hall/res/layout/nim_msg_fragment.xml index cd0aa3dba..41107a993 100644 --- a/app/src/module_public_chat_hall/res/layout/nim_msg_fragment.xml +++ b/app/src/module_public_chat_hall/res/layout/nim_msg_fragment.xml @@ -107,7 +107,7 @@ @@ -120,19 +120,19 @@ + android:scaleType="fitXY" + android:src="@drawable/nim_message_input_voice_pressed" /> @@ -140,7 +140,7 @@