Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit afeed29b authored by Selim Cinek's avatar Selim Cinek
Browse files

Changing messaging style and overall visual adoption

The messaging style is changed to a bubble-less design.
In addition are we also updating a few spacings in
order for it to look nicer / more balanced.

Test: manual
Change-Id: I43326bd8a23cd1f1d5964d2d6740fde99d29c7e4
parent cb8b985e
Loading
Loading
Loading
Loading
+43 −12
Original line number Diff line number Diff line
@@ -3981,7 +3981,10 @@ public class Notification implements Parcelable
            contentView.setViewVisibility(R.id.chronometer, View.GONE);
            contentView.setViewVisibility(R.id.header_text, View.GONE);
            contentView.setTextViewText(R.id.header_text, null);
            contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
            contentView.setTextViewText(R.id.header_text_secondary, null);
            contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
            contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
            contentView.setViewVisibility(R.id.time_divider, View.GONE);
            contentView.setViewVisibility(R.id.time, View.GONE);
            contentView.setImageViewIcon(R.id.profile_badge, null);
@@ -4012,7 +4015,7 @@ public class Notification implements Parcelable

            final Bundle ex = mN.extras;
            updateBackgroundColor(contentView);
            bindNotificationHeader(contentView, p.ambient);
            bindNotificationHeader(contentView, p.ambient, p.headerTextSecondary);
            bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
            boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
            if (p.title != null) {
@@ -4295,12 +4298,14 @@ public class Notification implements Parcelable
            return null;
        }

        private void bindNotificationHeader(RemoteViews contentView, boolean ambient) {
        private void bindNotificationHeader(RemoteViews contentView, boolean ambient,
                CharSequence secondaryHeaderText) {
            bindSmallIcon(contentView, ambient);
            bindHeaderAppName(contentView, ambient);
            if (!ambient) {
                // Ambient view does not have these
                bindHeaderText(contentView);
                bindHeaderTextSecondary(contentView, secondaryHeaderText);
                bindHeaderChronometerAndTime(contentView);
                bindProfileBadge(contentView);
            }
@@ -4369,6 +4374,17 @@ public class Notification implements Parcelable
            }
        }

        private void bindHeaderTextSecondary(RemoteViews contentView, CharSequence secondaryText) {
            if (!TextUtils.isEmpty(secondaryText)) {
                contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
                        processLegacyText(secondaryText)));
                setTextViewColorSecondary(contentView, R.id.header_text_secondary);
                contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
                contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
                setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider);
            }
        }

        /**
         * @hide
         */
@@ -4602,7 +4618,7 @@ public class Notification implements Parcelable
                    ambient ? R.layout.notification_template_ambient_header
                            : R.layout.notification_template_header);
            resetNotificationHeader(header);
            bindNotificationHeader(header, ambient);
            bindNotificationHeader(header, ambient, null);
            if (colorized != null) {
                mN.extras.putBoolean(EXTRA_COLORIZED, colorized);
            } else {
@@ -6269,7 +6285,7 @@ public class Notification implements Parcelable
        public RemoteViews makeContentView(boolean increasedHeight) {
            mBuilder.mOriginalActions = mBuilder.mActions;
            mBuilder.mActions = new ArrayList<>();
            RemoteViews remoteViews = makeBigContentView();
            RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
            mBuilder.mActions = mBuilder.mOriginalActions;
            mBuilder.mOriginalActions = null;
            return remoteViews;
@@ -6304,23 +6320,31 @@ public class Notification implements Parcelable
         */
        @Override
        public RemoteViews makeBigContentView() {
            return makeBigContentView(false /* showRightIcon */);
        }

        @NonNull
        private RemoteViews makeBigContentView(boolean showRightIcon) {
            CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
                    ? super.mBigContentTitle
                    : mConversationTitle;
            boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
            if (isOneToOne) {
                // Let's add the conversationTitle in case we didn't have one before and all
                // messages are from the same sender
                conversationTitle = createConversationTitleFromMessages();
            } else if (hasOnlyWhiteSpaceSenders()) {
            if (hasOnlyWhiteSpaceSenders()) {
                isOneToOne = true;
            }
            boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
            RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                    mBuilder.getMessagingLayoutResource(),
                    mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
                            .hideLargeIcon(isOneToOne).alwaysShowReply(true));
                            .hideLargeIcon(!showRightIcon || isOneToOne)
                            .headerTextSecondary(conversationTitle)
                            .alwaysShowReply(showRightIcon));
            addExtras(mBuilder.mN.extras);
            // also update the end margin if there is an image
            int endMargin = R.dimen.notification_content_margin_end;
            if (mBuilder.mN.hasLargeIcon() && showRightIcon) {
                endMargin = R.dimen.notification_content_plus_picture_margin_end;
            }
            contentView.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
            contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
                    mBuilder.resolveContrastColor());
            contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
@@ -6387,7 +6411,7 @@ public class Notification implements Parcelable
         */
        @Override
        public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
            RemoteViews remoteViews = makeBigContentView();
            RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
            remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
            return remoteViews;
        }
@@ -8854,6 +8878,7 @@ public class Notification implements Parcelable
        boolean ambient = false;
        CharSequence title;
        CharSequence text;
        CharSequence headerTextSecondary;
        boolean hideLargeIcon;
        public boolean alwaysShowReply;

@@ -8862,6 +8887,7 @@ public class Notification implements Parcelable
            ambient = false;
            title = null;
            text = null;
            headerTextSecondary = null;
            return this;
        }

@@ -8880,6 +8906,11 @@ public class Notification implements Parcelable
            return this;
        }

        final StandardTemplateParams headerTextSecondary(CharSequence text) {
            this.headerTextSecondary = text;
            return this;
        }

        final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
            this.alwaysShowReply = alwaysShowReply;
            return this;
+26 −20
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ public class NotificationHeaderView extends ViewGroup {
    private final int mGravity;
    private View mAppName;
    private View mHeaderText;
    private View mSecondaryHeaderText;
    private OnClickListener mExpandClickListener;
    private HeaderTouchListener mTouchListener = new HeaderTouchListener();
    private ImageView mExpandButton;
@@ -58,7 +59,6 @@ public class NotificationHeaderView extends ViewGroup {
    private boolean mShowExpandButtonAtEnd;
    private boolean mShowWorkBadgeAtEnd;
    private Drawable mBackground;
    private int mHeaderBackgroundHeight;
    private boolean mEntireHeaderClickable;
    private boolean mExpandOnlyOnButton;
    private boolean mAcceptAllTouches;
@@ -68,7 +68,7 @@ public class NotificationHeaderView extends ViewGroup {
        @Override
        public void getOutline(View view, Outline outline) {
            if (mBackground != null) {
                outline.setRect(0, 0, getWidth(), mHeaderBackgroundHeight);
                outline.setRect(0, 0, getWidth(), getHeight());
                outline.setAlpha(1f);
            }
        }
@@ -91,8 +91,6 @@ public class NotificationHeaderView extends ViewGroup {
        Resources res = getResources();
        mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
        mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
        mHeaderBackgroundHeight = res.getDimensionPixelSize(
                R.dimen.notification_header_background_height);
        mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);

        int[] attrIds = { android.R.attr.gravity };
@@ -106,6 +104,7 @@ public class NotificationHeaderView extends ViewGroup {
        super.onFinishInflate();
        mAppName = findViewById(com.android.internal.R.id.app_name_text);
        mHeaderText = findViewById(com.android.internal.R.id.header_text);
        mSecondaryHeaderText = findViewById(com.android.internal.R.id.header_text_secondary);
        mExpandButton = findViewById(com.android.internal.R.id.expand_button);
        mIcon = findViewById(com.android.internal.R.id.icon);
        mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
@@ -137,24 +136,31 @@ public class NotificationHeaderView extends ViewGroup {
        if (totalWidth > givenWidth) {
            int overFlow = totalWidth - givenWidth;
            // We are overflowing, lets shrink the app name first
            final int appWidth = mAppName.getMeasuredWidth();
            if (overFlow > 0 && mAppName.getVisibility() != GONE && appWidth > mChildMinWidth) {
                int newSize = appWidth - Math.min(appWidth - mChildMinWidth, overFlow);
                int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
                mAppName.measure(childWidthSpec, wrapContentHeightSpec);
                overFlow -= appWidth - newSize;
            overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName,
                    mChildMinWidth);

            // still overflowing, we shrink the header text
            overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0);

            // still overflowing, finally we shrink the secondary header text
            shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText,
                    0);
        }
        mTotalWidth = Math.min(totalWidth, givenWidth);
        setMeasuredDimension(givenWidth, givenHeight);
    }
            // still overflowing, finaly we shrink the header text
            if (overFlow > 0 && mHeaderText.getVisibility() != GONE) {

    private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
            int minimumWidth) {
        final int oldWidth = targetView.getMeasuredWidth();
        if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
            // we're still too big
                final int textWidth = mHeaderText.getMeasuredWidth();
                int newSize = Math.max(0, textWidth - overFlow);
            int newSize = Math.max(minimumWidth, oldWidth - overFlow);
            int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
                mHeaderText.measure(childWidthSpec, wrapContentHeightSpec);
            }
            targetView.measure(childWidthSpec, heightSpec);
            overFlow -= oldWidth - newSize;
        }
        mTotalWidth = Math.min(totalWidth, givenWidth);
        setMeasuredDimension(givenWidth, givenHeight);
        return overFlow;
    }

    @Override
@@ -228,7 +234,7 @@ public class NotificationHeaderView extends ViewGroup {
    @Override
    protected void onDraw(Canvas canvas) {
        if (mBackground != null) {
            mBackground.setBounds(0, 0, getWidth(), mHeaderBackgroundHeight);
            mBackground.setBounds(0, 0, getWidth(), getHeight());
            mBackground.draw(canvas);
        }
    }
+15 −35
Original line number Diff line number Diff line
@@ -20,17 +20,12 @@ import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.app.Notification;
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Pools;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -41,7 +36,6 @@ import android.widget.LinearLayout;
import android.widget.RemoteViews;

import com.android.internal.R;
import com.android.internal.util.NotificationColorUtil;

import java.util.ArrayList;
import java.util.List;
@@ -60,12 +54,12 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
    private int mLayoutColor;
    private CharSequence mAvatarName = "";
    private Icon mAvatarIcon;
    private ColorFilter mMessageBackgroundFilter;
    private int mTextColor;
    private List<MessagingMessage> mMessages;
    private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>();
    private boolean mFirstLayout;
    private boolean mIsHidingAnimated;
    private boolean mNeedsGeneratedAvatar;

    public MessagingGroup(@NonNull Context context) {
        super(context);
@@ -94,27 +88,19 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
        mAvatarView = findViewById(R.id.message_icon);
    }

    public void setSender(CharSequence sender) {
        if (sender == null) {
            mAvatarView.setVisibility(GONE);
            mSenderName.setVisibility(GONE);
            setGravity(Gravity.END);
            mMessageBackgroundFilter = new PorterDuffColorFilter(mLayoutColor,
                    PorterDuff.Mode.SRC_ATOP);
            mTextColor = NotificationColorUtil.isColorLight(mLayoutColor) ? getNormalTextColor()
                    : Color.WHITE;
        } else {
            mSenderName.setText(sender);
    public void setSender(Notification.Person sender) {
        mSenderName.setText(sender.getName());
        mNeedsGeneratedAvatar = sender.getIcon() == null;
        if (!mNeedsGeneratedAvatar) {
            setAvatar(sender.getIcon());
        }
        mAvatarView.setVisibility(VISIBLE);
        mSenderName.setVisibility(VISIBLE);
            setGravity(Gravity.START);
            mMessageBackgroundFilter = null;
        mTextColor = getNormalTextColor();
    }
    }

    private int getNormalTextColor() {
        return mContext.getColor(R.color.notification_primary_text_color_light);
        return mContext.getColor(R.color.notification_secondary_text_color_light);
    }

    public void setAvatar(Icon icon) {
@@ -207,10 +193,6 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
        return mSenderName.getText();
    }

    public void setSenderVisible(boolean visible) {
        mSenderName.setVisibility(visible ? VISIBLE : GONE);
    }

    public static void dropCache() {
        sInstancePool = new Pools.SynchronizedPool<>(10);
    }
@@ -317,12 +299,6 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
                mMessageContainer.removeView(message);
                mMessageContainer.addView(message, messageIndex);
            }
            // Let's make sure the message color is correct
            Drawable targetDrawable = message.getBackground();

            if (targetDrawable != null) {
                targetDrawable.mutate().setColorFilter(mMessageBackgroundFilter);
            }
            message.setTextColor(mTextColor);
        }
        mMessages = group;
@@ -390,4 +366,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
    public MessagingLinearLayout getMessageContainer() {
        return mMessageContainer;
    }

    public boolean needsGeneratedAvatar() {
        return mNeedsGeneratedAvatar;
    }
}
+22 −35
Original line number Diff line number Diff line
@@ -69,7 +69,6 @@ public class MessagingLayout extends FrameLayout {
    private List<MessagingMessage> mMessages = new ArrayList<>();
    private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
    private MessagingLinearLayout mMessagingLinearLayout;
    private View mContractedMessage;
    private boolean mShowHistoricMessages;
    private ArrayList<MessagingGroup> mGroups = new ArrayList<>();
    private TextView mTitleView;
@@ -81,6 +80,7 @@ public class MessagingLayout extends FrameLayout {
    private Icon mLargeIcon;
    private boolean mIsOneToOne;
    private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
    private Notification.Person mUser;

    public MessagingLayout(@NonNull Context context) {
        super(context);
@@ -129,6 +129,7 @@ public class MessagingLayout extends FrameLayout {
        Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
        List<Notification.MessagingStyle.Message> newHistoricMessages
                = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
        setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON));
        mConversationTitle = null;
        TextView headerText = findViewById(R.id.header_text);
        if (headerText != null) {
@@ -152,7 +153,6 @@ public class MessagingLayout extends FrameLayout {
        mMessages = messages;
        mHistoricMessages = historicMessages;

        updateContractedMessage();
        updateHistoricMessageVisibility();
        updateTitleAndNamesDisplay();
    }
@@ -163,12 +163,10 @@ public class MessagingLayout extends FrameLayout {
        for (int i = 0; i < mGroups.size(); i++) {
            MessagingGroup group = mGroups.get(i);
            CharSequence senderName = group.getSenderName();
            if (TextUtils.isEmpty(senderName)) {
            if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) {
                continue;
            }
            boolean visible = !mIsOneToOne;
            group.setSenderVisible(visible);
            if ((visible || mLargeIcon == null) && !uniqueNames.containsKey(senderName)) {
            if (!uniqueNames.containsKey(senderName)) {
                char c = senderName.charAt(0);
                if (uniqueCharacters.containsKey(c)) {
                    // this character was already used, lets make it more unique. We first need to
@@ -192,7 +190,8 @@ public class MessagingLayout extends FrameLayout {
            // Let's now set the avatars
            MessagingGroup group = mGroups.get(i);
            CharSequence senderName = group.getSenderName();
            if (TextUtils.isEmpty(senderName) || (mIsOneToOne && mLargeIcon != null)) {
            if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)
                    || (mIsOneToOne && mLargeIcon != null)) {
                continue;
            }
            String symbol = uniqueNames.get(senderName);
@@ -207,7 +206,7 @@ public class MessagingLayout extends FrameLayout {
            // Let's now set the avatars
            MessagingGroup group = mGroups.get(i);
            CharSequence senderName = group.getSenderName();
            if (TextUtils.isEmpty(senderName)) {
            if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) {
                continue;
            }
            if (mIsOneToOne && mLargeIcon != null) {
@@ -234,7 +233,7 @@ public class MessagingLayout extends FrameLayout {
        canvas.drawCircle(radius, radius, radius, mPaint);
        boolean needDarkText  = ColorUtils.calculateLuminance(color) > 0.5f;
        mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
        mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.75f : mAvatarSize * 0.4f);
        mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
        int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
        canvas.drawText(symbol, radius, yPos, mTextPaint);
        return Icon.createWithBitmap(bitmap);
@@ -270,11 +269,15 @@ public class MessagingLayout extends FrameLayout {
        mIsOneToOne = oneToOne;
    }

    public void setUser(Notification.Person user) {
        mUser = user;
    }

    private void addMessagesToGroups(List<MessagingMessage> historicMessages,
            List<MessagingMessage> messages) {
        // Let's first find our groups!
        List<List<MessagingMessage>> groups = new ArrayList<>();
        List<CharSequence> senders = new ArrayList<>();
        List<Notification.Person> senders = new ArrayList<>();

        // Lets first find the groups
        findGroups(historicMessages, messages, groups, senders);
@@ -283,7 +286,8 @@ public class MessagingLayout extends FrameLayout {
        createGroupViews(groups, senders);
    }

    private void createGroupViews(List<List<MessagingMessage>> groups, List<CharSequence> senders) {
    private void createGroupViews(List<List<MessagingMessage>> groups,
            List<Notification.Person> senders) {
        mGroups.clear();
        for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
            List<MessagingMessage> group = groups.get(groupIndex);
@@ -314,7 +318,7 @@ public class MessagingLayout extends FrameLayout {

    private void findGroups(List<MessagingMessage> historicMessages,
            List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
            List<CharSequence> senders) {
            List<Notification.Person> senders) {
        CharSequence currentSenderKey = null;
        List<MessagingMessage> currentGroup = null;
        int histSize = historicMessages.size();
@@ -327,35 +331,22 @@ public class MessagingLayout extends FrameLayout {
            }
            boolean isNewGroup = currentGroup == null;
            Notification.Person sender = message.getMessage().getSenderPerson();
            CharSequence key = sender.getKey() == null ? sender.getName() : sender.getKey();
            CharSequence key = sender == null ? null
                    : sender.getKey() == null ? sender.getName() : sender.getKey();
            isNewGroup |= !TextUtils.equals(key, currentSenderKey);
            if (isNewGroup) {
                currentGroup = new ArrayList<>();
                groups.add(currentGroup);
                senders.add(sender.getName());
                if (sender == null) {
                    sender = mUser;
                }
                senders.add(sender);
                currentSenderKey = key;
            }
            currentGroup.add(message);
        }
    }

    private void updateContractedMessage() {
        for (int i = mMessages.size() - 1; i >= 0; i--) {
            MessagingMessage m = mMessages.get(i);
            // Incoming messages have a non-empty sender.
            if (!TextUtils.isEmpty(m.getMessage().getSender())) {
                mContractedMessage = m;
                return;
            }
        }
        if (!mMessages.isEmpty()) {
            // No incoming messages, fall back to outgoing message
            mContractedMessage = mMessages.get(mMessages.size() - 1);
            return;
        }
        mContractedMessage = null;
    }

    /**
     * Creates new messages, reusing existing ones if they are available.
     *
@@ -431,10 +422,6 @@ public class MessagingLayout extends FrameLayout {
        }
    }

    public View getContractedMessage() {
        return mContractedMessage;
    }

    public MessagingLinearLayout getMessagingLinearLayout() {
        return mMessagingLinearLayout;
    }
+0 −26
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2017 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License
  -->

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    android:tint="#14000000">
    <corners android:radius="4dp" />
    <padding android:bottom="6dp"
        android:left="8dp"
        android:right="8dp"
        android:top="6dp" />
</shape>
Loading