From 687d6e9bc0bf305750118a1a1f788b1cef8efe34 Mon Sep 17 00:00:00 2001 From: zlzw <583819556@qq.com> Date: Fri, 15 Mar 2024 16:06:18 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E5=88=86=E4=BA=AB=20add=20=E6=8E=A8?= =?UTF-8?q?=E9=80=81=EF=BC=88=E6=90=9C=E7=B4=A2=E8=BF=98=E6=B2=A1=E5=81=9A?= =?UTF-8?q?=E3=80=81=E5=8D=A1=E7=89=87=E8=BF=98=E6=B2=A1=E5=81=9A=EF=BC=89?= =?UTF-8?q?=20add=20=E6=89=93=E6=8B=9B=E5=91=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../share/adapters/InternalShareAdapter.java | 84 ++++++ .../share/adapters/ShareAppAdapter.java | 32 +- .../com/yunbao/share/bean/ShareBuilder.java | 1 + .../com/yunbao/share/platform/Internal.java | 35 +++ .../share/ui/AppInternalShareDialog.java | 141 +++++++++ .../com/yunbao/share/ui/SharePopDialog.java | 11 +- .../share/ui/ShareSuccessNotifyDialog.java | 87 ++++++ .../main/res/drawable/btn_internal_radio.xml | 5 + .../main/res/layout/dialog_shrea_internal.xml | 119 ++++++++ .../main/res/layout/item_internal_user.xml | 114 +++++++ .../res/layout/view_share_success_notify.xml | 66 ++++ .../src/main/res/mipmap/bg_share_success.png | Bin 0 -> 187300 bytes Share/src/main/res/mipmap/ic_share_btn.png | Bin 0 -> 1838 bytes Share/src/main/res/mipmap/ic_share_friend.png | Bin 0 -> 37719 bytes .../src/main/res/mipmap/ic_share_success.png | Bin 0 -> 4360 bytes Share/src/main/res/values-en/strings.xml | 7 + Share/src/main/res/values/strings.xml | 7 + .../com/yunbao/share/ExampleUnitTest.java | 4 - .../java/com/shayu/phonelive/AppContext.java | 32 ++ .../MessageSayHiDialogTagListAdapter.java | 53 ++++ .../yunbao/common/bean/MessageSayHiBean.java | 46 +++ .../common/bean/MessageSayHiStartBean.java | 16 + .../common/bean/MessageUserInfoBean.java | 19 ++ .../dialog/MessageChatNotifyDialog.java | 96 ++++++ .../dialog/MessageSayHiNotifyDialog.java | 154 ++++++++++ .../com/yunbao/common/http/PDLiveApi.java | 13 + .../common/http/live/LiveNetManager.java | 73 ++++- .../content/MessageChatCardContent.java | 282 ++++++++++++++++++ .../provider/MessageChatCardItemProvider.java | 54 ++++ .../com/yunbao/common/utils/AppManager.java | 7 + .../utils/MessageChatNotifyManager.java | 62 ++++ .../utils/MessageSayHiNotifyManager.java | 224 ++++++++++++++ .../yunbao/common/views/AbsViewHolder.java | 3 + .../bg_msg_address_book_user_btn_fan.xml | 0 .../bg_msg_address_book_user_btn_follow.xml | 0 .../bg_msg_address_book_user_btn_mutual.xml | 0 .../main/res/drawable/bg_msg_list_search.xml | 0 .../drawable/bg_view_msg_chat_notify_btn.xml | 9 + .../main/res/drawable/view_chat_top_tag.xml | 0 .../view_layout_address_book_not_data.xml | 27 ++ .../view_layout_address_book_not_search.xml | 12 +- .../src/main/res/layout/view_layout_msg.xml | 28 ++ .../res/layout/view_layout_msg_not_search.xml | 27 ++ .../layout/view_message_chat_new_notify.xml | 65 ++++ .../layout/view_message_say_hi_now_notify.xml | 130 ++++++++ .../main/res/layout/view_msg_chat_top_tag.xml | 6 +- .../res/mipmap-xxhdpi/bg_dialog_say_hi.png | Bin 0 -> 172694 bytes .../mipmap-xxhdpi/bg_dialog_say_hi_avatar.png | Bin 0 -> 35279 bytes .../mipmap-xxhdpi/bg_dialog_say_hi_btn.png | Bin 0 -> 1487 bytes .../res/mipmap-xxhdpi/bg_message_push.png | Bin 0 -> 152915 bytes .../ic_addressbook_not_search.png | Bin 0 -> 113149 bytes .../ic_message_not_chat_list.png | Bin 0 -> 154190 bytes .../mipmap-xxhdpi/ic_message_not_search.png | Bin 0 -> 86817 bytes common/src/main/res/values-en-rUS/string.xml | 8 + common/src/main/res/values-zh-rHK/strings.xml | 8 + common/src/main/res/values-zh-rTW/strings.xml | 9 + common/src/main/res/values-zh/strings.xml | 8 + common/src/main/res/values/strings.xml | 8 + .../yunbao/main/activity/MainActivity.java | 3 + .../main/activity/MsgAddressBookActivity.java | 5 + .../MainMsgAddressBookListAdapter.java | 17 +- .../fragment/MainMessageChatFragment.java | 3 + .../main/fragment/MyAddressBookFragment.java | 16 +- .../ConversationIMListManager.java | 2 +- .../yunbao/main/views/MainHomeViewHolder.java | 2 + .../main/views/MainMessageViewHolder.java | 16 +- 66 files changed, 2225 insertions(+), 31 deletions(-) create mode 100644 Share/src/main/java/com/yunbao/share/adapters/InternalShareAdapter.java create mode 100644 Share/src/main/java/com/yunbao/share/platform/Internal.java create mode 100644 Share/src/main/java/com/yunbao/share/ui/AppInternalShareDialog.java create mode 100644 Share/src/main/java/com/yunbao/share/ui/ShareSuccessNotifyDialog.java create mode 100644 Share/src/main/res/drawable/btn_internal_radio.xml create mode 100644 Share/src/main/res/layout/dialog_shrea_internal.xml create mode 100644 Share/src/main/res/layout/item_internal_user.xml create mode 100644 Share/src/main/res/layout/view_share_success_notify.xml create mode 100644 Share/src/main/res/mipmap/bg_share_success.png create mode 100644 Share/src/main/res/mipmap/ic_share_btn.png create mode 100644 Share/src/main/res/mipmap/ic_share_friend.png create mode 100644 Share/src/main/res/mipmap/ic_share_success.png create mode 100644 common/src/main/java/com/yunbao/common/adapter/MessageSayHiDialogTagListAdapter.java create mode 100644 common/src/main/java/com/yunbao/common/bean/MessageSayHiBean.java create mode 100644 common/src/main/java/com/yunbao/common/bean/MessageSayHiStartBean.java create mode 100644 common/src/main/java/com/yunbao/common/dialog/MessageChatNotifyDialog.java create mode 100644 common/src/main/java/com/yunbao/common/dialog/MessageSayHiNotifyDialog.java create mode 100644 common/src/main/java/com/yunbao/common/message/content/MessageChatCardContent.java create mode 100644 common/src/main/java/com/yunbao/common/provider/MessageChatCardItemProvider.java create mode 100644 common/src/main/java/com/yunbao/common/utils/MessageChatNotifyManager.java create mode 100644 common/src/main/java/com/yunbao/common/utils/MessageSayHiNotifyManager.java rename {main => common}/src/main/res/drawable/bg_msg_address_book_user_btn_fan.xml (100%) rename {main => common}/src/main/res/drawable/bg_msg_address_book_user_btn_follow.xml (100%) rename {main => common}/src/main/res/drawable/bg_msg_address_book_user_btn_mutual.xml (100%) rename {main => common}/src/main/res/drawable/bg_msg_list_search.xml (100%) create mode 100644 common/src/main/res/drawable/bg_view_msg_chat_notify_btn.xml rename {main => common}/src/main/res/drawable/view_chat_top_tag.xml (100%) create mode 100644 common/src/main/res/layout/view_layout_address_book_not_data.xml rename live/src/main/res/layout/view_layout_msg.xml => common/src/main/res/layout/view_layout_address_book_not_search.xml (61%) create mode 100644 common/src/main/res/layout/view_layout_msg.xml create mode 100644 common/src/main/res/layout/view_layout_msg_not_search.xml create mode 100644 common/src/main/res/layout/view_message_chat_new_notify.xml create mode 100644 common/src/main/res/layout/view_message_say_hi_now_notify.xml rename {main => common}/src/main/res/layout/view_msg_chat_top_tag.xml (83%) create mode 100644 common/src/main/res/mipmap-xxhdpi/bg_dialog_say_hi.png create mode 100644 common/src/main/res/mipmap-xxhdpi/bg_dialog_say_hi_avatar.png create mode 100644 common/src/main/res/mipmap-xxhdpi/bg_dialog_say_hi_btn.png create mode 100644 common/src/main/res/mipmap-xxhdpi/bg_message_push.png create mode 100644 common/src/main/res/mipmap-xxhdpi/ic_addressbook_not_search.png create mode 100644 common/src/main/res/mipmap-xxhdpi/ic_message_not_chat_list.png create mode 100644 common/src/main/res/mipmap-xxhdpi/ic_message_not_search.png diff --git a/Share/src/main/java/com/yunbao/share/adapters/InternalShareAdapter.java b/Share/src/main/java/com/yunbao/share/adapters/InternalShareAdapter.java new file mode 100644 index 0000000..d245fc1 --- /dev/null +++ b/Share/src/main/java/com/yunbao/share/adapters/InternalShareAdapter.java @@ -0,0 +1,84 @@ +package com.yunbao.share.adapters; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RadioButton; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.pdlive.shayu.R; +import com.yunbao.common.glide.ImgLoader; +import com.yunbao.common.views.weight.ClipPathCircleImage; + +import java.util.ArrayList; +import java.util.List; + +import io.rong.imkit.conversationlist.model.SingleConversation; +import io.rong.imlib.model.Conversation; + +public class InternalShareAdapter extends RecyclerView.Adapter { + List listData = new ArrayList<>(); + int selectPosition = -1; + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_internal_user, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(listData.get(position), position); + } + + @Override + public int getItemCount() { + return listData.size(); + } + + public void setList(List listData) { + if (listData == null) { + listData = new ArrayList<>(); + } + this.listData = listData; + notifyDataSetChanged(); + + } + + public int getSelectPosition() { + return selectPosition; + } + + public class ViewHolder extends RecyclerView.ViewHolder { + ClipPathCircleImage mAvatar; + TextView userName; + TextView content; + RadioButton radioButton; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + mAvatar = itemView.findViewById(R.id.rc_conversation_portrait); + userName = itemView.findViewById(R.id.rc_conversation_title); + content = itemView.findViewById(R.id.rc_conversation_content); + radioButton = itemView.findViewById(R.id.btn); + } + + public void bind(SingleConversation item, int position) { + ImgLoader.display(itemView.getContext(), item.mCore.getPortraitUrl(), mAvatar); + userName.setText(item.mCore.getConversationTitle()); + content.setText(""); + if (selectPosition == position) { + radioButton.setChecked(true); + } else { + radioButton.setChecked(false); + } + itemView.setOnClickListener(view -> { + selectPosition = position; + notifyDataSetChanged(); + }); + } + } +} diff --git a/Share/src/main/java/com/yunbao/share/adapters/ShareAppAdapter.java b/Share/src/main/java/com/yunbao/share/adapters/ShareAppAdapter.java index 5c10648..977367b 100644 --- a/Share/src/main/java/com/yunbao/share/adapters/ShareAppAdapter.java +++ b/Share/src/main/java/com/yunbao/share/adapters/ShareAppAdapter.java @@ -18,6 +18,7 @@ import com.yunbao.share.ICallback; import com.yunbao.share.bean.ShareBuilder; import com.yunbao.share.platform.FacebookShare; import com.yunbao.share.platform.Instagram; +import com.yunbao.share.platform.Internal; import com.yunbao.share.platform.Line; import com.yunbao.share.platform.MessengerShare; import com.yunbao.share.platform.TwitterShare; @@ -30,6 +31,7 @@ import java.util.List; public class ShareAppAdapter extends RecyclerView.Adapter { private Context mContext; private List list; + ShareCallback shareCallback; public ShareAppAdapter(Context mContext) { list = new ArrayList<>(); @@ -69,6 +71,9 @@ public class ShareAppAdapter extends RecyclerView.Adapter() { + @Override + public void onItemClick(String toUid, int position) { + if (position == -1) { + callback.onFailure(); + return; + } + builder.setUid(toUid); + new ShareSuccessNotifyDialog(mContext, builder) + .showDialog(); + callback.onSuccess(); + } + }).showDialog(); + } +} diff --git a/Share/src/main/java/com/yunbao/share/ui/AppInternalShareDialog.java b/Share/src/main/java/com/yunbao/share/ui/AppInternalShareDialog.java new file mode 100644 index 0000000..3bf8e35 --- /dev/null +++ b/Share/src/main/java/com/yunbao/share/ui/AppInternalShareDialog.java @@ -0,0 +1,141 @@ +package com.yunbao.share.ui; + +import android.content.Context; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.lxj.xpopup.XPopup; +import com.pdlive.shayu.R; +import com.yunbao.common.bean.MessageChatUserBean; +import com.yunbao.common.dialog.AbsDialogFullScreenPopupWindow; +import com.yunbao.common.interfaces.OnItemClickListener; +import com.yunbao.common.utils.ToastUtil; +import com.yunbao.common.utils.ViewUtils; +import com.yunbao.share.adapters.InternalShareAdapter; +import com.yunbao.share.bean.ShareBuilder; + +import java.util.ArrayList; +import java.util.List; + +import io.rong.imkit.conversationlist.model.SingleConversation; +import io.rong.imkit.userinfo.RongUserInfoManager; +import io.rong.imkit.widget.refresh.SmartRefreshLayout; +import io.rong.imkit.widget.refresh.api.RefreshLayout; +import io.rong.imkit.widget.refresh.listener.OnLoadMoreListener; +import io.rong.imkit.widget.refresh.listener.OnRefreshListener; +import io.rong.imkit.widget.refresh.wrapper.RongRefreshHeader; +import io.rong.imlib.IRongCoreCallback; +import io.rong.imlib.IRongCoreEnum; +import io.rong.imlib.RongCoreClient; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.model.Conversation; + +public class AppInternalShareDialog extends AbsDialogFullScreenPopupWindow { + EditText search; + OnItemClickListener onItemClickListener; + InternalShareAdapter adapter; + protected SmartRefreshLayout mRefreshLayout; + protected RecyclerView mList; + long startTime = 0; + List listData = new ArrayList<>(); + + public AppInternalShareDialog(@NonNull Context context) { + super(context); + } + + @Override + public void buildDialog(XPopup.Builder builder) { + + } + + public AppInternalShareDialog setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + return this; + } + + @Override + public int bindLayoutId() { + return R.layout.dialog_shrea_internal; + } + + @Override + protected void onCreate() { + super.onCreate(); + findViewById(R.id.btn_back).setOnClickListener(view -> { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(null, -1); + } + dismiss(); + }); + findViewById(R.id.btn_share).setOnClickListener(view -> { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(listData.get(adapter.getSelectPosition()).mCore.getTargetId(), adapter.getSelectPosition()); + } + dismiss(); + }); + search = findViewById(R.id.search); + mList = findViewById(R.id.recyclerView); + mRefreshLayout = findViewById(R.id.rc_refresh); + adapter = new InternalShareAdapter(); + mList.setAdapter(adapter); + initRefreshView(); + + refreshData(); + } + + + protected void initRefreshView() { + this.mRefreshLayout.setNestedScrollingEnabled(false); + this.mRefreshLayout.setRefreshHeader(new RongRefreshHeader(this.getContext())); + this.mRefreshLayout.setRefreshFooter(new RongRefreshHeader(this.getContext())); + this.mRefreshLayout.setOnRefreshListener(new OnRefreshListener() { + public void onRefresh(@NonNull RefreshLayout refreshLayout) { + onConversationListRefresh(refreshLayout); + } + }); + this.mRefreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() { + public void onLoadMore(@NonNull RefreshLayout refreshLayout) { + onConversationListLoadMore(); + } + }); + } + + private void onConversationListLoadMore() { + refreshData(); + } + + private void onConversationListRefresh(RefreshLayout refreshLayout) { + listData.clear(); + startTime = 0; + refreshData(); + } + + private void refreshData() { + RongCoreClient.getInstance().getConversationListByPage(new IRongCoreCallback.ResultCallback>() { + @Override + public void onSuccess(List conversations) { + if (conversations.isEmpty()) { + mRefreshLayout.finishLoadMoreWithNoMoreData(); + return; + } + List tmp = new ArrayList<>(); + for (Conversation conversation : conversations) { + tmp.add(new SingleConversation(getContext(), conversation)); + } + startTime = conversations.get(conversations.size() - 1).getSentTime(); + listData.addAll(tmp); + adapter.setList(listData); + mRefreshLayout.finishRefresh(true); + } + + @Override + public void onError(IRongCoreEnum.CoreErrorCode e) { + ToastUtil.show("出错了:" + e.getMessage()); + } + }, startTime, 10, Conversation.ConversationType.PRIVATE); + } +} diff --git a/Share/src/main/java/com/yunbao/share/ui/SharePopDialog.java b/Share/src/main/java/com/yunbao/share/ui/SharePopDialog.java index e70c7a6..6683254 100644 --- a/Share/src/main/java/com/yunbao/share/ui/SharePopDialog.java +++ b/Share/src/main/java/com/yunbao/share/ui/SharePopDialog.java @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.lxj.xpopup.XPopup; +import com.lxj.xpopup.util.XPopupUtils; import com.makeramen.roundedimageview.RoundedImageView; import com.pdlive.shayu.R; import com.yunbao.common.dialog.AbsDialogPopupWindow; @@ -70,7 +71,6 @@ public class SharePopDialog extends AbsDialogPopupWindow { @Override public void buildDialog(XPopup.Builder builder) { - } @Override @@ -89,6 +89,13 @@ public class SharePopDialog extends AbsDialogPopupWindow { info = findViewById(R.id.share_info); link = findViewById(R.id.share_link); adapter = new ShareAppAdapter(getContext()); + adapter.setOnShareStatusListener(new ShareAppAdapter.ShareCallback(){ + @Override + public void onSuccess() { + super.onSuccess(); + dismiss(); + } + }); list.setLayoutManager(new GridLayoutManager(getContext(), 3)); list.setAdapter(adapter); initData(); @@ -101,7 +108,7 @@ public class SharePopDialog extends AbsDialogPopupWindow { data.add(builder(ShareBuilder.APP_TWITTER)); data.add(builder(ShareBuilder.APP_WHATSAPP)); data.add(builder(ShareBuilder.APP_MESSENGER)); - //data.add(builder(ShareBuilder.APP_INSTAGRAM)); + data.add(builder(ShareBuilder.APP_INTERNAL)); adapter.setList(data); String url; if (shareLink == null) { diff --git a/Share/src/main/java/com/yunbao/share/ui/ShareSuccessNotifyDialog.java b/Share/src/main/java/com/yunbao/share/ui/ShareSuccessNotifyDialog.java new file mode 100644 index 0000000..bb9c2c0 --- /dev/null +++ b/Share/src/main/java/com/yunbao/share/ui/ShareSuccessNotifyDialog.java @@ -0,0 +1,87 @@ +package com.yunbao.share.ui; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Handler; +import android.os.Looper; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.lxj.xpopup.XPopup; +import com.lxj.xpopup.enums.PopupAnimation; +import com.pdlive.shayu.R; +import com.yunbao.common.bean.MessageUserInfoBean; +import com.yunbao.common.custom.RatioRoundImageView; +import com.yunbao.common.dialog.AbsDialogPositionPopupWindow; +import com.yunbao.common.glide.ImgLoader; +import com.yunbao.common.utils.DpUtil; +import com.yunbao.common.utils.StringUtil; +import com.yunbao.share.bean.ShareBuilder; + +import io.rong.imkit.utils.RouteUtils; +import io.rong.imlib.model.Conversation; + +public class ShareSuccessNotifyDialog extends AbsDialogPositionPopupWindow { + private ShareBuilder bean; + private DialogInterface.OnDismissListener onDismissListener; + + private TextView anchorName; + private RatioRoundImageView avatar; + + + public ShareSuccessNotifyDialog(@NonNull Context context) { + super(context); + } + + public ShareSuccessNotifyDialog(@NonNull Context context, ShareBuilder bean) { + super(context); + this.bean = bean; + } + + public ShareSuccessNotifyDialog setOnDismissListener(DialogInterface.OnDismissListener onDismissListener) { + this.onDismissListener = onDismissListener; + return this; + } + + @Override + public void buildDialog(XPopup.Builder builder) { + builder.positionByWindowCenter(true); + builder.moveUpToKeyboard(false); + builder.dismissOnTouchOutside(false); + builder.dismissOnBackPressed(false); + builder.isTouchThrough(true); + builder.isClickThrough(true); + builder.isCenterHorizontal(true); + builder.hasShadowBg(false); + builder.animationDuration(500); + builder.popupAnimation(PopupAnimation.ScaleAlphaFromCenter); + builder.offsetY(DpUtil.dp2px(60)); + } + + @Override + public int bindLayoutId() { + return R.layout.view_share_success_notify; + } + + @Override + protected void onShow() { + super.onShow(); + } + + @Override + protected void onCreate() { + super.onCreate(); + findViewById(R.id.liveGo).setOnClickListener(v -> { + Conversation.ConversationType type = Conversation.ConversationType.PRIVATE; + RouteUtils.routeToConversationActivity(mContext, type, bean.getUid(), null); + }); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + dismiss(); + if (onDismissListener != null) { + onDismissListener.onDismiss(null); + } + }, 5000); + } + +} diff --git a/Share/src/main/res/drawable/btn_internal_radio.xml b/Share/src/main/res/drawable/btn_internal_radio.xml new file mode 100644 index 0000000..a08b676 --- /dev/null +++ b/Share/src/main/res/drawable/btn_internal_radio.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Share/src/main/res/layout/dialog_shrea_internal.xml b/Share/src/main/res/layout/dialog_shrea_internal.xml new file mode 100644 index 0000000..67cdeaa --- /dev/null +++ b/Share/src/main/res/layout/dialog_shrea_internal.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Share/src/main/res/layout/item_internal_user.xml b/Share/src/main/res/layout/item_internal_user.xml new file mode 100644 index 0000000..5968e73 --- /dev/null +++ b/Share/src/main/res/layout/item_internal_user.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Share/src/main/res/layout/view_share_success_notify.xml b/Share/src/main/res/layout/view_share_success_notify.xml new file mode 100644 index 0000000..e17beeb --- /dev/null +++ b/Share/src/main/res/layout/view_share_success_notify.xml @@ -0,0 +1,66 @@ + + + + + + + + + +