小礼物逻辑上传
This commit is contained in:
parent
71a7b47d42
commit
1f8025599b
@ -6,6 +6,37 @@ public class QuickGiftingEvent extends BaseModel {
|
||||
private int index;
|
||||
private String svgaName;
|
||||
private boolean show =false;
|
||||
private boolean reminder = false;
|
||||
|
||||
private String giftRemainingQuantity;
|
||||
private String hotNum;
|
||||
|
||||
public String getHotNum() {
|
||||
return hotNum;
|
||||
}
|
||||
|
||||
public QuickGiftingEvent setHotNum(String hotNum) {
|
||||
this.hotNum = hotNum;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getGiftRemainingQuantity() {
|
||||
return giftRemainingQuantity;
|
||||
}
|
||||
|
||||
public QuickGiftingEvent setGiftRemainingQuantity(String giftRemainingQuantity) {
|
||||
this.giftRemainingQuantity = giftRemainingQuantity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isReminder() {
|
||||
return reminder;
|
||||
}
|
||||
|
||||
public QuickGiftingEvent setReminder(boolean reminder) {
|
||||
this.reminder = reminder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isShow() {
|
||||
return show;
|
||||
|
@ -60,6 +60,13 @@ public class IMLoginManager extends BaseCacheManager {
|
||||
public boolean getRedPoint() {
|
||||
return !TextUtils.isEmpty(getString("RedPoint")) && !TextUtils.equals(getString("RedPoint"), "1");
|
||||
}
|
||||
public void setQuickGiftIfFirst() {
|
||||
put("quick_gift_if_first", "1");
|
||||
}
|
||||
|
||||
public boolean getQuickGiftIfFirst() {
|
||||
return TextUtils.isEmpty(getString("quick_gift_if_first")) || !TextUtils.equals(getString("quick_gift_if_first"), "1");
|
||||
}
|
||||
|
||||
public void setSudGameMin() {
|
||||
put("SudGameMin", "0");
|
||||
|
@ -2,6 +2,7 @@ package com.yunbao.common.utils;
|
||||
|
||||
import static com.yunbao.common.CommonAppConfig.isGetNewWrap;
|
||||
import static com.yunbao.common.utils.RouteUtil.PATH_COIN;
|
||||
import static com.yunbao.common.utils.RouteUtil.PATH_REWARD;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
@ -25,6 +26,7 @@ import com.yunbao.common.bean.VideoBean;
|
||||
import com.yunbao.common.bean.VideoListBean;
|
||||
import com.yunbao.common.event.JavascriptInterfaceEvent;
|
||||
import com.yunbao.common.event.LiveRoomChangeEvent;
|
||||
import com.yunbao.common.event.QuickGiftingEvent;
|
||||
import com.yunbao.common.http.HttpCallback;
|
||||
import com.yunbao.common.http.HttpClient;
|
||||
import com.yunbao.common.http.LiveHttpUtil;
|
||||
@ -124,10 +126,10 @@ public class JavascriptInterfaceUtils {
|
||||
url = CommonAppConfig.HOST + url;
|
||||
if (url.contains("?")) {
|
||||
url += "&uid=" + CommonAppConfig.getInstance().getUid() + "&token="
|
||||
+ CommonAppConfig.getInstance().getToken()+ "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
|
||||
+ CommonAppConfig.getInstance().getToken() + "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
|
||||
} else {
|
||||
url += "?uid=" + CommonAppConfig.getInstance().getUid() + "&token="
|
||||
+ CommonAppConfig.getInstance().getToken()+ "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
|
||||
+ CommonAppConfig.getInstance().getToken() + "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
|
||||
}
|
||||
if (LiveZhuangBana) {
|
||||
RouteUtil.forwardLiveZhuangBanActivity(url, false);
|
||||
@ -147,10 +149,10 @@ public class JavascriptInterfaceUtils {
|
||||
url = CommonAppConfig.HOST + url;
|
||||
if (url.contains("?")) {
|
||||
url += "&uid=" + CommonAppConfig.getInstance().getUid() + "&token="
|
||||
+ CommonAppConfig.getInstance().getToken()+ "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
|
||||
+ CommonAppConfig.getInstance().getToken() + "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
|
||||
} else {
|
||||
url += "?uid=" + CommonAppConfig.getInstance().getUid() + "&token="
|
||||
+ CommonAppConfig.getInstance().getToken()+ "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
|
||||
+ CommonAppConfig.getInstance().getToken() + "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
|
||||
}
|
||||
if (LiveZhuangBana) {
|
||||
RouteUtil.forwardLiveZhuangBanActivity(url, title);
|
||||
@ -259,11 +261,12 @@ public class JavascriptInterfaceUtils {
|
||||
|
||||
/**
|
||||
* 跳转直播并打开礼物栏选中礼物
|
||||
*
|
||||
* @param liveId 直播间id
|
||||
* @param giftId 礼物id
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void androidMethodLookToLiveGift(String liveId,String giftId){
|
||||
public void androidMethodLookToLiveGift(String liveId, String giftId) {
|
||||
LiveHttpUtil.getLiveInfo(liveId, new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(int code, String msg, String[] info) {
|
||||
@ -300,6 +303,7 @@ public class JavascriptInterfaceUtils {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void androidMethodLookToLive(String liveId) {
|
||||
Bus.get().post(new JavascriptInterfaceEvent()
|
||||
@ -598,6 +602,18 @@ public class JavascriptInterfaceUtils {
|
||||
.setMethod("clickLogOffAccount"));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void androidClickToTaskPage() {
|
||||
ARouter.getInstance().build(PATH_REWARD).withString("url",
|
||||
CommonAppConfig.HOST + "/index.php?g=Appapi&m=task&a=index" + "&uid=" + CommonAppConfig.getInstance().getUid()
|
||||
+ "&token=" + CommonAppConfig.getInstance().getToken() + "&tabIndex=1"+ "&isZh=" + (WordUtil.isNewZh() ? "1" : "0")).navigation();
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void androidQuickGiftRemainingQuantity(int giftRemainingQuantity) {
|
||||
Bus.get().post(new QuickGiftingEvent().setGiftRemainingQuantity(String.valueOf(giftRemainingQuantity)));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void androidFansGroupBuy(String id) {
|
||||
Bus.get().post(new JavascriptInterfaceEvent()
|
||||
@ -644,8 +660,9 @@ public class JavascriptInterfaceUtils {
|
||||
Bus.get().post(new JavascriptInterfaceEvent()
|
||||
.setMethod("androidCancelAnchorAttention"));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void androidLnsufficientBalanceClick(String msg){
|
||||
public void androidLnsufficientBalanceClick(String msg) {
|
||||
DialogUitl.showSimpleDialog(mContext, msg, new DialogUitl.SimpleCallback2() {
|
||||
@Override
|
||||
public void onCancelClick() {
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -9,9 +9,9 @@ ext {
|
||||
]
|
||||
manifestPlaceholders = [
|
||||
//正式、
|
||||
serverHost : "https://napi.yaoulive.com",
|
||||
// serverHost : "https://napi.yaoulive.com",
|
||||
// 测试
|
||||
// serverHost : " https://ceshi.yaoulive.com",
|
||||
serverHost : " https://ceshi.yaoulive.com",
|
||||
|
||||
|
||||
|
||||
|
@ -1774,13 +1774,25 @@ public class LiveAudienceActivity extends LiveActivity {
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onQuickGiftingEvent(QuickGiftingEvent event) {
|
||||
if (manager != null) {
|
||||
|
||||
|
||||
if (event.isShow()) {
|
||||
manager.onQuickGifting2(true);
|
||||
if (TextUtils.isEmpty(event.getHotNum())) {
|
||||
if (TextUtils.isEmpty(event.getGiftRemainingQuantity())) {
|
||||
if (event.isReminder()) {
|
||||
manager.onQuickGifting3();
|
||||
} else {
|
||||
if (event.isShow()) {
|
||||
manager.onQuickGifting2(true);
|
||||
} else {
|
||||
manager.onQuickGifting(event.getIndex(), event.getSvgaName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
manager.setGiftRemainingQuantity(event.getGiftRemainingQuantity());
|
||||
}
|
||||
} else {
|
||||
manager.onQuickGifting(event.getIndex(), event.getSvgaName());
|
||||
manager.setHotNum(event.getHotNum());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,17 @@ public class SendQuickGiftModel extends BaseModel {
|
||||
private String userNicename;
|
||||
@SerializedName("quick_gift_number")
|
||||
private String quickGiftNumber;
|
||||
@SerializedName("hot_num")
|
||||
private String hotNum;
|
||||
|
||||
public String getHotNum() {
|
||||
return hotNum;
|
||||
}
|
||||
|
||||
public SendQuickGiftModel setHotNum(String hotNum) {
|
||||
this.hotNum = hotNum;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
|
@ -38,6 +38,7 @@ import com.yunbao.common.bean.XydCompleteModel;
|
||||
import com.yunbao.common.event.AllServerNotifyEvent;
|
||||
import com.yunbao.common.event.CustomFullServiceNotifyEvent;
|
||||
import com.yunbao.common.event.GiftWallIlluminateEvent;
|
||||
import com.yunbao.common.event.QuickGiftingEvent;
|
||||
import com.yunbao.common.event.SendBlindGiftEvent;
|
||||
import com.yunbao.common.http.HttpCallback;
|
||||
import com.yunbao.common.http.HttpClient;
|
||||
@ -767,10 +768,11 @@ public class SocketRyClient {
|
||||
SendQuickGiftModel sendQuickGiftModel = GsonUtils.fromJson(map.toString(), SendQuickGiftModel.class);
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
stringBuffer.append(sendQuickGiftModel.getUserNicename())
|
||||
.append(WordUtil.isNewZh()?"送出了":"sent")
|
||||
.append(WordUtil.isNewZh() ? "送出了" : "sent")
|
||||
.append(sendQuickGiftModel.getQuickGiftNumber())
|
||||
.append(WordUtil.isNewZh()?"個小PD":"small pandas");
|
||||
.append(WordUtil.isNewZh() ? "個小PD" : "small pandas");
|
||||
systemChatMessage2(stringBuffer.toString());
|
||||
Bus.get().post(new QuickGiftingEvent().setHotNum(sendQuickGiftModel.getHotNum()));
|
||||
break;
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import com.lxj.xpopup.XPopup;
|
||||
import com.umeng.analytics.MobclickAgent;
|
||||
import com.yunbao.common.CommonAppConfig;
|
||||
import com.yunbao.common.Constants;
|
||||
import com.yunbao.common.bean.IMLoginModel;
|
||||
import com.yunbao.common.bean.NewPeopleInfo;
|
||||
import com.yunbao.common.bean.QuickGiftSendGiftModel;
|
||||
import com.yunbao.common.dialog.LiveNewRolePopup;
|
||||
@ -279,6 +280,45 @@ public class LiveAudienceViewHolder extends AbsLiveViewHolder {
|
||||
mProcessResultUtil = new ProcessResultUtil((FragmentActivity) mContext);
|
||||
quick_gift_remaining_quantity.setVisibility(View.GONE);
|
||||
time_text.setVisibility(View.GONE);
|
||||
ViewClicksAntiShake.clicksAntiShake(time_text, new ViewClicksAntiShake.ViewClicksCallBack() {
|
||||
@Override
|
||||
public void onViewClicks() {
|
||||
IMLoginModel userInfo = IMLoginManager.get(mContext).getUserInfo();
|
||||
StringBuffer urlString = new StringBuffer();
|
||||
urlString.append(CommonAppConfig.HOST)
|
||||
.append("/h5/exchangeShop/index.html?uid=")
|
||||
.append(userInfo.getId())
|
||||
.append("&token=")
|
||||
.append(userInfo.getToken()).append("&isZh=")
|
||||
.append(WordUtil.isNewZh() ? "1" : 0);
|
||||
Bundle bundle1 = new Bundle();
|
||||
bundle1.putString("url", urlString.toString());
|
||||
LiveHDDialogFragment liveHDDialogFragment = new LiveHDDialogFragment();
|
||||
liveHDDialogFragment.setArguments(bundle1);
|
||||
liveHDDialogFragment.show(((LiveAudienceActivity) mContext).getSupportFragmentManager(), "LiveHDDialogFragment");
|
||||
}
|
||||
});
|
||||
pd_pao.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
IMLoginModel userInfo = IMLoginManager.get(mContext).getUserInfo();
|
||||
StringBuffer urlString = new StringBuffer();
|
||||
urlString.append(CommonAppConfig.HOST)
|
||||
.append("/h5/exchangeShop/index.html?uid=")
|
||||
.append(userInfo.getId())
|
||||
.append("&token=")
|
||||
.append(userInfo.getToken()).append("&isZh=")
|
||||
.append(WordUtil.isNewZh() ? "1" : 0);
|
||||
Bundle bundle1 = new Bundle();
|
||||
bundle1.putString("url", urlString.toString());
|
||||
LiveHDDialogFragment liveHDDialogFragment = new LiveHDDialogFragment();
|
||||
liveHDDialogFragment.setArguments(bundle1);
|
||||
liveHDDialogFragment.show(((LiveAudienceActivity) mContext).getSupportFragmentManager(), "LiveHDDialogFragment");
|
||||
Bus.get().post(new QuickGiftingEvent().setReminder(true));
|
||||
MobclickAgent.onEvent(mContext, "small_pd_gifts", "长按");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
pd_pao.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
@ -293,6 +333,7 @@ public class LiveAudienceViewHolder extends AbsLiveViewHolder {
|
||||
Bus.get().post(new QuickGiftingEvent().setIndex(quickGiftingIndex).setSvgaName(randomName));
|
||||
mQuickGiftRemainingQuantity = mQuickGiftRemainingQuantity - 1;
|
||||
quick_gift_remaining_quantity.setText(String.valueOf(mQuickGiftRemainingQuantity));
|
||||
MobclickAgent.onEvent(mContext, "small_pd_gifts", "连点");
|
||||
}
|
||||
|
||||
}
|
||||
@ -322,11 +363,22 @@ public class LiveAudienceViewHolder extends AbsLiveViewHolder {
|
||||
}
|
||||
mQuickGiftRemainingQuantity = mQuickGiftRemainingQuantity - 1;
|
||||
quick_gift_remaining_quantity.setText(String.valueOf(mQuickGiftRemainingQuantity));
|
||||
MobclickAgent.onEvent(mContext, "small_pd_gifts", "连点"+quickGiftingIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setGiftRemainingQuantity(String giftRemainingQuantity) {
|
||||
mQuickGiftRemainingQuantity = Integer.parseInt(giftRemainingQuantity);
|
||||
if (mQuickGiftRemainingQuantity > 0) {
|
||||
quick_gift_remaining_quantity.setVisibility(View.VISIBLE);
|
||||
quick_gift_remaining_quantity.setText(String.valueOf(mQuickGiftRemainingQuantity));
|
||||
} else {
|
||||
quick_gift_remaining_quantity.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private int mQuickGiftRemainingQuantity = 0, mIfViewingDurationComplete;
|
||||
|
||||
public void upDataQuickGift(int quickGiftRemainingQuantity, int ifViewingDurationComplete) {
|
||||
|
@ -387,6 +387,7 @@ public class LiveRoomViewHolder extends AbsViewHolder implements View.OnClickLis
|
||||
private FrameLayout pa_pao_layout;
|
||||
private LinearLayout combo_layout;
|
||||
private TextView combo_number;
|
||||
private View quick_gift_reminder ;
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onUpdata(String str) {
|
||||
@ -711,8 +712,19 @@ public class LiveRoomViewHolder extends AbsViewHolder implements View.OnClickLis
|
||||
public void onQuickGifting2(boolean show) {
|
||||
if (show) {
|
||||
combo_layout.setVisibility(View.GONE);
|
||||
if (IMLoginManager.get(mContext).getQuickGiftIfFirst()) {
|
||||
quick_gift_reminder.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
quick_gift_reminder.setVisibility(View.GONE);
|
||||
}
|
||||
IMLoginManager.get(mContext).setQuickGiftIfFirst();
|
||||
}
|
||||
}
|
||||
public void onQuickGifting3() {
|
||||
IMLoginManager.get(mContext).setQuickGiftIfFirst();
|
||||
quick_gift_reminder.setVisibility(View.GONE);
|
||||
|
||||
}
|
||||
|
||||
public void onQuickGifting(int index, String svgaName) {
|
||||
pa_pao_layout.setVisibility(View.VISIBLE);
|
||||
@ -1553,8 +1565,10 @@ public class LiveRoomViewHolder extends AbsViewHolder implements View.OnClickLis
|
||||
pa_pao_layout = (FrameLayout) findViewById(R.id.pa_pao_layout);
|
||||
combo_layout = (LinearLayout) findViewById(R.id.combo_layout);
|
||||
combo_number = (TextView) findViewById(R.id.combo_number);
|
||||
quick_gift_reminder = findViewById(R.id.quick_gift_reminder);
|
||||
pa_pao_layout.setVisibility(View.GONE);
|
||||
combo_layout.setVisibility(View.GONE);
|
||||
|
||||
}
|
||||
|
||||
private FrameLayout redPacket;
|
||||
@ -2531,11 +2545,11 @@ public class LiveRoomViewHolder extends AbsViewHolder implements View.OnClickLis
|
||||
* @param rank 小时榜
|
||||
*/
|
||||
public void setHourRankData(long rank) {
|
||||
if (mHourRank != null) {
|
||||
if (mHotText != null) {
|
||||
if (rank == 0) {
|
||||
mHourRank.setText(R.string.no_rank_data);
|
||||
mHotText.setText(R.string.no_rank_data);
|
||||
} else {
|
||||
mHourRank.setText(String.format(mContext.getString(R.string.hour_rank), formatBigNum.formatBigNum(rank + "")));
|
||||
mHotText.setText(String.format(mContext.getString(R.string.hour_rank), formatBigNum.formatBigNum(rank + "")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2555,12 +2569,14 @@ public class LiveRoomViewHolder extends AbsViewHolder implements View.OnClickLis
|
||||
* @param hot 热度值
|
||||
*/
|
||||
public void setHotData(String hot) {
|
||||
if (mHotText != null && !TextUtils.isEmpty(hot)) {
|
||||
mHotText.setText(hot);
|
||||
if (mHourRank != null && !TextUtils.isEmpty(hot)) {
|
||||
mHourRank.setText(hot);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setHotNum(String hotNum) {
|
||||
setHotData(formatBigNum.formatBigNum(hotNum));
|
||||
}
|
||||
/**
|
||||
* 更新心愿单进度
|
||||
*
|
||||
|
@ -56,6 +56,7 @@ import com.yunbao.common.event.LiveFloatEvent;
|
||||
import com.yunbao.common.event.LiveGiftDialogEvent;
|
||||
import com.yunbao.common.event.LiveRoomChangeEvent;
|
||||
import com.yunbao.common.event.LiveSudGamePopupShowOrHideEvent;
|
||||
import com.yunbao.common.event.QuickGiftingEvent;
|
||||
import com.yunbao.common.event.SendBlindGiftEvent;
|
||||
import com.yunbao.common.glide.ImgLoader;
|
||||
import com.yunbao.common.http.CommonHttpConsts;
|
||||
@ -235,6 +236,22 @@ public class PortraitLiveManager implements LivePlayListener, SocketMessageListe
|
||||
}
|
||||
}
|
||||
|
||||
public void onQuickGifting3() {
|
||||
if (mLiveRoomViewHolder != null) {
|
||||
mLiveRoomViewHolder.onQuickGifting3();
|
||||
}
|
||||
}
|
||||
|
||||
public void setGiftRemainingQuantity(String giftRemainingQuantity) {
|
||||
if (mLiveAudienceViewHolder!=null){
|
||||
mLiveAudienceViewHolder.setGiftRemainingQuantity(giftRemainingQuantity);
|
||||
}
|
||||
}
|
||||
public void setHotNum(String hotNum) {
|
||||
if (mLiveRoomViewHolder!=null){
|
||||
mLiveRoomViewHolder.setHotNum(hotNum);
|
||||
}
|
||||
}
|
||||
public PortraitLiveManager(Activity context, Intent intent) {
|
||||
this.mContext = context;
|
||||
this.mIntent = intent;
|
||||
@ -846,8 +863,8 @@ public class PortraitLiveManager implements LivePlayListener, SocketMessageListe
|
||||
mLivePlayViewHolder.release();
|
||||
}
|
||||
if (mLiveRoomViewHolder != null) {
|
||||
mLiveRoomViewHolder.removeFromParent();
|
||||
mLiveRoomViewHolder.clearData();
|
||||
mLiveRoomViewHolder.removeFromParent();
|
||||
mLiveRoomViewHolder.countDownTimerTrickery = null;
|
||||
}
|
||||
|
||||
|
@ -506,6 +506,7 @@
|
||||
android:textColor="#FFB403"
|
||||
android:textSize="8sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
@ -2509,6 +2509,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="384dp"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/combo_layout"
|
||||
android:layout_width="wrap_content"
|
||||
@ -2533,4 +2534,14 @@
|
||||
android:textSize="58sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/quick_gift_reminder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="125dp"
|
||||
android:src="@mipmap/background_quick_gift_reminder"
|
||||
android:visibility="gone" />
|
||||
</RelativeLayout>
|
||||
|
Loading…
Reference in New Issue
Block a user