Add MVP classes for messages

This commit is contained in:
Paul Schaub 2018-02-23 02:23:45 +01:00
parent 7bc1207581
commit eb49f77a2b
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
28 changed files with 695 additions and 55 deletions

View file

@ -24,7 +24,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -23,6 +23,7 @@ import org.jxmpp.jid.EntityBareJid;
import java.util.List;
import de.vanitasvitae.slam.mvp.view.ConversationFragment;
import de.vanitasvitae.slam.xmpp.message.AbstractMessage;
/**
* Model-View-Presenter contract for the {@link ConversationFragment}.
@ -31,7 +32,7 @@ import de.vanitasvitae.slam.mvp.view.ConversationFragment;
public interface ConversationContract {
interface View {
void addMessageItems(List<Message> messages, boolean end);
void addMessageItems(List<AbstractMessage> messages, boolean end);
void highlightMessageItem();
void correctMessageItem();
void navigateToContactProfile();

View file

@ -0,0 +1,30 @@
package de.vanitasvitae.slam.mvp.contracts.message;
/**
* Model-View-Presenter contract for an abstract message.
*/
public interface AbstractMessageContract {
interface View {
void setDirection(Direction direction);
void setStatusSending();
void setStatusSendingFailed();
void setStatusSent();
void setStatusRead();
void setSelected();
void displayMessageInformation();
void displayErrorMessage();
}
interface Presenter {
void onDeleteMessage();
void onReadMessage();
void onMessageClick();
void onMessageLongClick();
}
enum Direction {
sent, // We are the author (the message was sent from one of the users devices)
received, // We are not the author
}
}

View file

@ -0,0 +1,17 @@
package de.vanitasvitae.slam.mvp.contracts.message;
/**
* Created by Paul Schaub on 23.02.18.
*/
public interface AudioMessageContract {
interface View extends MediaMessageContract.View {
void setWaveFormPreview();
void setLength(int seconds);
void updatePlayProgress(float percent);
}
interface Presenter extends MediaMessageContract.Presenter {
}
}

View file

@ -0,0 +1,16 @@
package de.vanitasvitae.slam.mvp.contracts.message;
/**
* Created by Paul Schaub on 23.02.18.
*/
public interface ImageMessageContract {
interface View extends MediaMessageContract.View {
void setThumbnail();
void displayImage();
}
interface Presenter extends MediaMessageContract.Presenter {
}
}

View file

@ -0,0 +1,18 @@
package de.vanitasvitae.slam.mvp.contracts.message;
/**
* Created by Paul Schaub on 23.02.18.
*/
public interface MediaMessageContract {
interface View extends AbstractMessageContract.View {
void setDownloadable(boolean downloadable);
void setDownloaded(boolean downloaded);
void updateDownloadProgress(float percent);
void setSize(int bytes);
}
interface Presenter extends AbstractMessageContract.Presenter{
}
}

View file

@ -0,0 +1,16 @@
package de.vanitasvitae.slam.mvp.contracts.message;
/**
* Created by Paul Schaub on 23.02.18.
*/
public interface TextMessageContract {
interface View extends AbstractMessageContract.View {
void setContent(CharSequence content);
void setStatusCorrected();
}
interface Presenter extends AbstractMessageContract.Presenter {
void onCorrectMessage();
}
}

View file

@ -0,0 +1,16 @@
package de.vanitasvitae.slam.mvp.contracts.message;
/**
* Created by Paul Schaub on 23.02.18.
*/
public interface VideoMessageContract {
interface View extends MediaMessageContract.View {
void setThumbnail();
void displayVideo();
}
interface Presenter extends MediaMessageContract.Presenter {
}
}

View file

@ -72,7 +72,7 @@ public class DummyLoginPresenter implements LoginContract.Presenter {
public void run() {
view.hideProgressIndicator();
}
}, 2000);
}, 100);
// startActivity
handler.postDelayed(new Runnable() {
@ -80,6 +80,6 @@ public class DummyLoginPresenter implements LoginContract.Presenter {
public void run() {
view.navigateToMainActivity();
}
}, 2500);
}, 250);
}
}

View file

@ -22,8 +22,8 @@ import android.support.design.widget.AppBarLayout;
import android.support.design.widget.TabLayout;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import java.util.List;
@ -34,12 +34,14 @@ import de.vanitasvitae.slam.R;
import de.vanitasvitae.slam.mvp.PresenterFactory;
import de.vanitasvitae.slam.mvp.contracts.ContactDetailContract;
import de.vanitasvitae.slam.mvp.view.abstr.ThemedAppCompatActivity;
import de.vanitasvitae.slam.ui.NonPagingViewPager;
import de.vanitasvitae.slam.xmpp.Resource;
/**
* Main activity that hosts some fragments.
*/
public class ContactDetailActivity extends ThemedAppCompatActivity implements ContactDetailContract.View, AppBarLayout.OnOffsetChangedListener {
public class ContactDetailActivity extends ThemedAppCompatActivity
implements ContactDetailContract.View, AppBarLayout.OnOffsetChangedListener {
public static final String TAG = "Slam!";
@ -59,14 +61,14 @@ public class ContactDetailActivity extends ThemedAppCompatActivity implements Co
TabLayout tabLayout;
@BindView(R.id.contact_detail_viewpager)
ViewPager pager;
NonPagingViewPager pager;
@BindView(R.id.contact_detail_profile_circle)
CircleImageView profileCircle;
private int mMaxScrollSize;
private boolean isProfileCircleShown;
private int animateProfileCirclePercent = 20;
private int animateProfileCirclePercent = 30;
public ContactDetailActivity() {
this.presenter = PresenterFactory.getInstance().createContactDetailPresenter(this);
@ -80,11 +82,22 @@ public class ContactDetailActivity extends ThemedAppCompatActivity implements Co
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowTitleEnabled(false);
appBarLayout.addOnOffsetChangedListener(this);
pager.setAdapter(new DetailFragmentPagerAdapter(getSupportFragmentManager()));
tabLayout.setupWithViewPager(pager);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
if (mMaxScrollSize == 0)
@ -97,7 +110,8 @@ public class ContactDetailActivity extends ThemedAppCompatActivity implements Co
profileCircle.animate()
.scaleY(0).scaleX(0)
.setDuration(200)
.alpha(0)
.setDuration(300)
.start();
}
@ -106,6 +120,7 @@ public class ContactDetailActivity extends ThemedAppCompatActivity implements Co
profileCircle.animate()
.scaleY(1).scaleX(1)
.alpha(1)
.start();
}
}

View file

@ -109,7 +109,7 @@ public class ContactListFragment extends Fragment implements ContactListContract
public void onBindViewHolder(ContactListEntry holder, int position) {
final Contact contact = contacts.get(holder.getAdapterPosition());
holder.bind(contact);
holder.setOnAvatarClickListener(new View.OnClickListener() {
holder.setOnEntryClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
navigateToContactDetail(contact.getJid());

View file

@ -42,7 +42,9 @@ import butterknife.ButterKnife;
import de.vanitasvitae.slam.R;
import de.vanitasvitae.slam.mvp.PresenterFactory;
import de.vanitasvitae.slam.mvp.contracts.ConversationContract;
import de.vanitasvitae.slam.ui.ChatMessageEntry;
import de.vanitasvitae.slam.mvp.view.message.MessageAdapter;
import de.vanitasvitae.slam.mvp.view.message.MessageView;
import de.vanitasvitae.slam.xmpp.message.AbstractMessage;
/**
* Fragment that shows the conversation with a user.
@ -57,26 +59,13 @@ public class ConversationFragment extends Fragment implements ConversationContra
private final ConversationContract.Presenter presenter;
Map<String, Integer> messageIdIndizes = new HashMap<>();
List<Message> messages = new ArrayList<>();
List<AbstractMessage> messages = new ArrayList<>();
private final RecyclerView.Adapter<ChatMessageEntry> chatMessageAdapter = new RecyclerView.Adapter<ChatMessageEntry>() {
@Override
public ChatMessageEntry onCreateViewHolder(ViewGroup parent, int viewType) {
View messageView = LayoutInflater.from(getActivity()).inflate(R.layout.item_conversation_message, parent, false);
return new ChatMessageEntry(messageView);
}
private final MessageAdapter messageAdapter = new MessageAdapter(getContext()) {
@Override
public void onBindViewHolder(ChatMessageEntry holder, int position) {
Message message = messages.get(position);
String sender = message.getFrom().toString();
String role = "Member";
View content;
content = new TextView(getActivity());
((TextView)content).setText(message.getBody());
holder.bind(sender, role, content, "now");
public void onBindViewHolder(MessageView holder, int position) {
super.onBindViewHolder(holder, position);
holder.setOnAvatarClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -85,6 +74,11 @@ public class ConversationFragment extends Fragment implements ConversationContra
});
}
@Override
public AbstractMessage getItemAt(int position) {
return messages.get(position);
}
@Override
public int getItemCount() {
return messages.size();
@ -119,18 +113,18 @@ public class ConversationFragment extends Fragment implements ConversationContra
@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(chatMessageAdapter);
recyclerView.setAdapter(messageAdapter);
recyclerView.getAdapter().notifyDataSetChanged();
}
@Override
public void addMessageItems(List<Message> messages, boolean end) {
public void addMessageItems(List<AbstractMessage> messages, boolean end) {
if (end) {
this.messages.addAll(messages);
} else {
this.messages.addAll(0, messages);
}
chatMessageAdapter.notifyDataSetChanged();
messageAdapter.notifyDataSetChanged();
}
@Override

View file

@ -0,0 +1,36 @@
package de.vanitasvitae.slam.mvp.view.message;
import android.view.View;
import de.vanitasvitae.slam.mvp.contracts.message.AudioMessageContract;
import de.vanitasvitae.slam.xmpp.message.AudioMessage;
/**
* Created by Paul Schaub on 23.02.18.
*/
public class AudioMessageView extends MediaMessageView<AudioMessage> implements AudioMessageContract.View {
public AudioMessageView(View itemView) {
super(itemView);
}
@Override
public void bind(AudioMessage message) {
}
@Override
public void setWaveFormPreview() {
}
@Override
public void setLength(int seconds) {
}
@Override
public void updatePlayProgress(float percent) {
}
}

View file

@ -0,0 +1,31 @@
package de.vanitasvitae.slam.mvp.view.message;
import android.view.View;
import de.vanitasvitae.slam.mvp.contracts.message.ImageMessageContract;
import de.vanitasvitae.slam.xmpp.message.ImageMessage;
/**
* Created by Paul Schaub on 23.02.18.
*/
public class ImageMessageView extends MediaMessageView<ImageMessage> implements ImageMessageContract.View {
public ImageMessageView(View itemView) {
super(itemView);
}
@Override
public void bind(ImageMessage message) {
}
@Override
public void setThumbnail() {
}
@Override
public void displayImage() {
}
}

View file

@ -0,0 +1,78 @@
package de.vanitasvitae.slam.mvp.view.message;
import android.view.View;
import de.vanitasvitae.slam.mvp.contracts.message.AbstractMessageContract;
import de.vanitasvitae.slam.mvp.contracts.message.MediaMessageContract;
import de.vanitasvitae.slam.xmpp.message.MediaMessage;
/**
* Created by Paul Schaub on 23.02.18.
*/
public abstract class MediaMessageView<T extends MediaMessage> extends MessageView<T>
implements MediaMessageContract.View {
public MediaMessageView(View itemView) {
super(itemView);
}
@Override
public void setDirection(AbstractMessageContract.Direction direction) {
}
@Override
public void setStatusSending() {
}
@Override
public void setStatusSendingFailed() {
}
@Override
public void setStatusSent() {
}
@Override
public void setStatusRead() {
}
@Override
public void setSelected() {
}
@Override
public void displayMessageInformation() {
}
@Override
public void displayErrorMessage() {
}
@Override
public void setDownloadable(boolean downloadable) {
}
@Override
public void setDownloaded(boolean downloaded) {
}
@Override
public void updateDownloadProgress(float percent) {
}
@Override
public void setSize(int bytes) {
}
}

View file

@ -0,0 +1,87 @@
package de.vanitasvitae.slam.mvp.view.message;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import de.vanitasvitae.slam.R;
import de.vanitasvitae.slam.xmpp.message.AbstractMessage;
import de.vanitasvitae.slam.xmpp.message.AudioMessage;
import de.vanitasvitae.slam.xmpp.message.ImageMessage;
import de.vanitasvitae.slam.xmpp.message.TextMessage;
import de.vanitasvitae.slam.xmpp.message.VideoMessage;
/**
* Created by Paul Schaub on 23.02.18.
*/
public abstract class MessageAdapter extends RecyclerView.Adapter<MessageView> {
public static final int
TYPE_TEXT = 0,
TYPE_IMAGE = 1,
TYPE_VIDEO = 2,
TYPE_AUDIO = 3;
private Context context;
public MessageAdapter(Context context) {
this.context = context;
}
@Override
public MessageView onCreateViewHolder(ViewGroup parent, int viewType) {
View layout;
switch (viewType) {
case TYPE_TEXT:
layout = View.inflate(context, R.layout.item_conversation_message, null);
return new TextMessageView(layout);
case TYPE_IMAGE:
layout = View.inflate(context, R.layout.item_conversation_message, null);
return new ImageMessageView(layout);
case TYPE_VIDEO:
layout = View.inflate(context, R.layout.item_conversation_message, null);
return new VideoMessageView(layout);
case TYPE_AUDIO:
layout = View.inflate(context, R.layout.item_conversation_message, null);
return new AudioMessageView(layout);
}
throw new IllegalArgumentException("Illegal viewType: " + viewType);
}
@Override
public void onBindViewHolder(MessageView holder, int position) {
AbstractMessage message = getItemAt(position);
holder.bind(message);
}
@Override
public int getItemViewType(int position) {
AbstractMessage message = getItemAt(position);
if (message instanceof TextMessage) {
return TYPE_TEXT;
}
if (message instanceof ImageMessage) {
return TYPE_IMAGE;
}
if (message instanceof VideoMessage) {
return TYPE_VIDEO;
}
if (message instanceof AudioMessage) {
return TYPE_AUDIO;
}
return -1; // ERROR
}
public abstract AbstractMessage getItemAt(int position);
}

View file

@ -15,32 +15,72 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package de.vanitasvitae.slam.ui;
package de.vanitasvitae.slam.mvp.view.message;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.google.protobuf.Message;
import de.vanitasvitae.slam.R;
import de.vanitasvitae.slam.mvp.contracts.message.AbstractMessageContract;
import de.vanitasvitae.slam.xmpp.message.AbstractMessage;
/**
* Created by Paul Schaub on 30.01.18.
*/
public class ChatMessageEntry extends RecyclerView.ViewHolder {
public abstract class MessageView<T extends AbstractMessage> extends RecyclerView.ViewHolder
implements AbstractMessageContract.View {
public ChatMessageEntry(View itemView) {
public MessageView(View itemView) {
super(itemView);
}
public void bind(String sender, String role, View content, String date) {
((TextView)itemView.findViewById(R.id.message_sender)).setText(sender);
((TextView)itemView.findViewById(R.id.message_sender_role)).setText(role);
((RelativeLayout)itemView.findViewById(R.id.message_content)).addView(content);
((TextView)itemView.findViewById(R.id.message_date)).setText(date);
}
public abstract void bind(T message);
public void setOnAvatarClickListener(View.OnClickListener listener) {
itemView.findViewById(R.id.contact_image).setOnClickListener(listener);
}
@Override
public void setDirection(AbstractMessageContract.Direction direction) {
}
@Override
public void setStatusSending() {
}
@Override
public void setStatusSendingFailed() {
}
@Override
public void setStatusSent() {
}
@Override
public void setStatusRead() {
}
@Override
public void setSelected() {
}
@Override
public void displayMessageInformation() {
}
@Override
public void displayErrorMessage() {
}
}

View file

@ -0,0 +1,32 @@
package de.vanitasvitae.slam.mvp.view.message;
import android.view.View;
import de.vanitasvitae.slam.mvp.contracts.message.TextMessageContract;
import de.vanitasvitae.slam.xmpp.message.TextMessage;
/**
* Created by Paul Schaub on 23.02.18.
*/
public class TextMessageView extends MessageView<TextMessage>
implements TextMessageContract.View {
public TextMessageView(View itemView) {
super(itemView);
}
@Override
public void bind(TextMessage message) {
}
@Override
public void setContent(CharSequence content) {
}
@Override
public void setStatusCorrected() {
}
}

View file

@ -0,0 +1,32 @@
package de.vanitasvitae.slam.mvp.view.message;
import android.view.View;
import de.vanitasvitae.slam.mvp.contracts.message.VideoMessageContract;
import de.vanitasvitae.slam.xmpp.message.VideoMessage;
/**
* Created by Paul Schaub on 23.02.18.
*/
public class VideoMessageView extends MediaMessageView<VideoMessage>
implements VideoMessageContract.View {
public VideoMessageView(View itemView) {
super(itemView);
}
@Override
public void bind(VideoMessage message) {
}
@Override
public void setThumbnail() {
}
@Override
public void displayVideo() {
}
}

View file

@ -0,0 +1,46 @@
package de.vanitasvitae.slam.ui;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
/**
* Created by Paul Schaub on 14.02.18.
*/
public class NonPagingViewPager extends ViewPager {
private boolean pagingEnabled = false;
public NonPagingViewPager(@NonNull Context context) {
super(context);
}
public NonPagingViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (this.pagingEnabled) {
return super.onTouchEvent(event);
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (this.pagingEnabled) {
return super.onInterceptTouchEvent(event);
}
return false;
}
public void setPagingEnabled(boolean enabled) {
this.pagingEnabled = enabled;
}
}

View file

@ -0,0 +1,10 @@
package de.vanitasvitae.slam.xmpp;
/**
* Created by Paul Schaub on 23.02.18.
*/
public enum SentReadMarker {
sending,
sent,
read
}

View file

@ -0,0 +1,43 @@
package de.vanitasvitae.slam.xmpp.message;
import org.jxmpp.jid.Jid;
import java.util.Date;
/**
* Created by Paul Schaub on 23.02.18.
*/
public abstract class AbstractMessage {
private final Jid sender;
private final Jid recipient;
private final Date sent;
private boolean read = false;
public AbstractMessage(Jid sender, Jid recipient, Date sent) {
this.sender = sender;
this.recipient = recipient;
this.sent = sent;
}
public Jid getSender() {
return sender;
}
public Jid getRecipient() {
return recipient;
}
public Date getSent() {
return sent;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = true;
}
}

View file

@ -0,0 +1,15 @@
package de.vanitasvitae.slam.xmpp.message;
import org.jxmpp.jid.Jid;
import java.util.Date;
/**
* Created by Paul Schaub on 23.02.18.
*/
public class AudioMessage extends MediaMessage {
public AudioMessage(Jid sender, Jid recipient, Date sent) {
super(sender, recipient, sent);
}
}

View file

@ -0,0 +1,15 @@
package de.vanitasvitae.slam.xmpp.message;
import org.jxmpp.jid.Jid;
import java.util.Date;
/**
* Created by Paul Schaub on 23.02.18.
*/
public class ImageMessage extends MediaMessage {
public ImageMessage(Jid sender, Jid recipient, Date sent) {
super(sender, recipient, sent);
}
}

View file

@ -0,0 +1,15 @@
package de.vanitasvitae.slam.xmpp.message;
import org.jxmpp.jid.Jid;
import java.util.Date;
/**
* Created by Paul Schaub on 23.02.18.
*/
public abstract class MediaMessage extends AbstractMessage {
public MediaMessage(Jid sender, Jid recipient, Date sent) {
super(sender, recipient, sent);
}
}

View file

@ -0,0 +1,22 @@
package de.vanitasvitae.slam.xmpp.message;
import org.jxmpp.jid.Jid;
import java.util.Date;
/**
* Created by Paul Schaub on 23.02.18.
*/
public class TextMessage extends AbstractMessage {
private String content;
public TextMessage(Jid sender, Jid recipient, String content, Date sent) {
super(sender, recipient, sent);
this.content = content;
}
public String getContent() {
return content;
}
}

View file

@ -0,0 +1,15 @@
package de.vanitasvitae.slam.xmpp.message;
import org.jxmpp.jid.Jid;
import java.util.Date;
/**
* Created by Paul Schaub on 23.02.18.
*/
public class VideoMessage extends MediaMessage {
public VideoMessage(Jid sender, Jid recipient, Date sent) {
super(sender, recipient, sent);
}
}

View file

@ -35,7 +35,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:layout_scrollFlags="scroll|enterAlways"
/>
<LinearLayout
@ -45,7 +45,7 @@
android:orientation="vertical"
android:paddingTop="8dp"
android:gravity="center"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:layout_scrollFlags="scroll|enterAlways"
>
<TextView
@ -77,22 +77,22 @@
/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
<android.support.v4.widget.NestedScrollView
android:id="@+id/contact_detail.nested_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:layout_gravity="fill_vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<de.vanitasvitae.slam.ui.NonPagingViewPager
android:id="@+id/contact_detail.viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
<android.support.design.widget.FloatingActionButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:elevation="8dp"
android:layout_gravity="bottom|right|end"
android:src="@drawable/ic_chat_white_48dp"
android:layout_margin="@dimen/activity_horizontal_margin"
android:clickable="true"
android:focusable="true" />
</android.support.v4.widget.NestedScrollView>
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/contact_detail.profile_circle"