This commit is contained in:
Paul Schaub 2019-06-03 01:16:49 +02:00
parent 35d7a7c473
commit 308ca9c467
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
36 changed files with 823 additions and 113 deletions

View File

@ -77,10 +77,6 @@ dependencies {
transitive = true
}
/*
architecture components for database and lifecycle management
*/
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycleVersion"
@ -89,6 +85,10 @@ dependencies {
implementation "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
// ButterKnife for View Binding
implementation "com.jakewharton:butterknife:$butterKnifeVersion"
annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnifeVersion"
// support libraries
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'com.google.android.material:material:1.0.0'
@ -96,6 +96,7 @@ dependencies {
implementation 'androidx.vectordrawable:vectordrawable:1.0.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'

View File

@ -39,6 +39,7 @@
<activity
android:name=".ui.login.LoginActivity"
android:label="@string/title_activity_login" />
<activity android:name=".ui.login.AccountsActivity" />
<service android:name=".service.XmppStartedService" />
</application>

View File

@ -10,6 +10,8 @@ import org.mercury_im.messenger.ui.chat.ChatActivity;
import org.mercury_im.messenger.ui.chat.ChatInputFragment;
import org.mercury_im.messenger.ui.chat.ChatInputViewModel;
import org.mercury_im.messenger.ui.chat.ChatViewModel;
import org.mercury_im.messenger.ui.login.AccountsActivity;
import org.mercury_im.messenger.ui.login.AccountsViewModel;
import org.mercury_im.messenger.ui.login.LoginActivity;
import org.mercury_im.messenger.ui.login.LoginViewModel;
import org.mercury_im.messenger.ui.roster.RosterViewModel;
@ -27,6 +29,7 @@ public interface AppComponent {
void inject(MercuryImApplication mercuryImApplication);
// Views
void inject(MainActivity mainActivity);
@ -35,8 +38,11 @@ public interface AppComponent {
void inject(ChatActivity chatActivity);
void inject(AccountsActivity accountsActivity);
void inject(ChatInputFragment chatInputFragment);
// ViewModels
void inject(RosterViewModel rosterViewModel);
@ -47,6 +53,9 @@ public interface AppComponent {
void inject(LoginViewModel loginViewModel);
void inject(AccountsViewModel accountsViewModel);
// Services
void inject(XmppStartedService service);

View File

@ -19,6 +19,7 @@ import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.repository.ContactRepository;
import org.mercury_im.messenger.ui.chat.ChatActivity;
import org.mercury_im.messenger.ui.login.AccountsActivity;
import org.mercury_im.messenger.ui.login.LoginActivity;
import org.mercury_im.messenger.ui.settings.SettingsActivity;
@ -79,6 +80,10 @@ public class MainActivity extends AppCompatActivity {
case R.id.action_chat:
startActivity(new Intent(getApplicationContext(), ChatActivity.class));
return true;
case R.id.action_accounts:
startActivity(new Intent(getApplicationContext(), AccountsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);

View File

@ -2,6 +2,8 @@ package org.mercury_im.messenger.ui.chat;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -24,6 +26,8 @@ public class ChatInputFragment extends Fragment implements View.OnClickListener
private ChatInputViewModel mViewModel;
private OnChatInputActionListener actionListener;
public static ChatInputFragment newInstance() {
return new ChatInputFragment();
}
@ -42,7 +46,7 @@ public class ChatInputFragment extends Fragment implements View.OnClickListener
return view;
}
@Override
@Override2
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = ViewModelProviders.of(this).get(ChatInputViewModel.class);
@ -58,19 +62,50 @@ public class ChatInputFragment extends Fragment implements View.OnClickListener
});
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnChatInputActionListener) {
this.actionListener = (OnChatInputActionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnListFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
this.actionListener = null;
}
@Override
public void onClick(View view) {
switch (view.getId()) {
// Add media
case R.id.chat_field__button_attachment:
if (actionListener != null) {
actionListener.onButtonAddAttachementClicked();
}
Toast.makeText(getContext(), R.string.not_yet_implemented, Toast.LENGTH_SHORT).show();
break;
// Send message
case R.id.chat_field__button_send:
if (actionListener != null) {
actionListener.onComposingBodySend(textInput.getText().toString());
}
textInput.setText(null);
Toast.makeText(getContext(), "Send message clicked", Toast.LENGTH_SHORT).show();
break;
}
}
public interface OnChatInputActionListener {
void onButtonAddAttachementClicked();
void onComposingBodyChanged(String body);
void onComposingBodySend(String body);
}
}

View File

@ -0,0 +1,47 @@
package org.mercury_im.messenger.ui.login;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import androidx.fragment.app.FragmentActivity;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import javax.inject.Inject;
public class AccountsActivity extends FragmentActivity implements AccountsFragment.OnListFragmentInteractionListener {
@Inject
AccountRepository accountRepository;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_accounts);
MercuryImApplication.getApplication().getAppComponent().inject(this);
}
@Override
public void onListFragmentInteraction(AccountModel item) {
View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_account_details, null);
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setView(dialogView)
.setTitle(item.getJid())
.setPositiveButton("Save", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.create().show();
}
}

View File

@ -0,0 +1,104 @@
package org.mercury_im.messenger.ui.login;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel;
/**
* A fragment representing a list of Items.
* <p/>
* Activities containing this fragment MUST implement the {@link OnListFragmentInteractionListener}
* interface.
*/
public class AccountsFragment extends Fragment {
private OnListFragmentInteractionListener mListener;
AccountsViewModel viewModel;
private AccountsRecyclerViewAdapter adapter;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public AccountsFragment() {
}
// TODO: Customize parameter initialization
@SuppressWarnings("unused")
public static AccountsFragment newInstance() {
AccountsFragment fragment = new AccountsFragment();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(MercuryImApplication.TAG, "AccountsFragment.onCreateView");
View view = inflater.inflate(R.layout.fragment_account_list, container, false);
viewModel = ViewModelProviders.of(this).get(AccountsViewModel.class);
// Set the adapter
if (view instanceof RecyclerView) {
Context context = view.getContext();
RecyclerView recyclerView = (RecyclerView) view;
recyclerView.setLayoutManager(new LinearLayoutManager(context));
this.adapter = new AccountsRecyclerViewAdapter(viewModel.getAccounts().getValue(), mListener);
viewModel.getAccounts().observe(this, roomAccountModels -> adapter.setValues(roomAccountModels));
recyclerView.setAdapter(adapter);
}
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnListFragmentInteractionListener) {
mListener = (OnListFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnListFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p/>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnListFragmentInteractionListener {
// TODO: Update argument type and name
void onListFragmentInteraction(AccountModel item);
}
}

View File

@ -0,0 +1,87 @@
package org.mercury_im.messenger.ui.login;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;
import android.widget.TextView;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
import org.mercury_im.messenger.ui.login.AccountsFragment.OnListFragmentInteractionListener;
import java.util.ArrayList;
import java.util.List;
import de.hdodenhof.circleimageview.CircleImageView;
public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter<AccountsRecyclerViewAdapter.ViewHolder> {
private final List<RoomAccountModel> mValues;
private final OnListFragmentInteractionListener mListener;
public AccountsRecyclerViewAdapter(List<RoomAccountModel> items, OnListFragmentInteractionListener listener) {
mValues = items != null ? items : new ArrayList<>();
mListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_view_item_account, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
AccountModel account = mValues.get(position);
holder.jid.setText(account.getJid());
holder.enabled.setChecked(account.getEnabled());
holder.accountModel = account;
holder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mListener) {
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mListener.onListFragmentInteraction(holder.accountModel);
}
}
});
}
public void setValues(List<RoomAccountModel> values) {
this.mValues.clear();
if (values != null) {
mValues.addAll(values);
}
notifyDataSetChanged();
}
@Override
public int getItemCount() {
Log.d(MercuryImApplication.TAG, "Accounts Item Count: " + mValues.size());
return mValues.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public AccountModel accountModel;
public final View mView;
public final CircleImageView avatar;
public final TextView jid;
public final Switch enabled;
public ViewHolder(View view) {
super(view);
mView = view;
avatar = view.findViewById(R.id.avatar_account);
jid = view.findViewById(R.id.text_account_jid);
enabled = view.findViewById(R.id.switch_account_enabled);
}
}
}

View File

@ -0,0 +1,31 @@
package org.mercury_im.messenger.ui.login;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
import java.util.List;
import javax.inject.Inject;
public class AccountsViewModel extends ViewModel {
@Inject
AccountRepository repository;
private LiveData<List<RoomAccountModel>> accounts;
@Inject
public AccountsViewModel() {
super();
MercuryImApplication.getApplication().getAppComponent().inject(this);
accounts = repository.getAllAccountsLive();
}
public LiveData<List<RoomAccountModel>> getAccounts() {
return accounts;
}
}

View File

@ -0,0 +1,21 @@
package org.mercury_im.messenger.ui.login;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
public class AddAccountDialog extends AlertDialog {
protected AddAccountDialog(@NonNull Context context, int themeResId) {
super(context, themeResId);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}

View File

@ -5,6 +5,8 @@ import androidx.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
@ -25,6 +27,9 @@ import org.mercury_im.messenger.service.XmppStartedService;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* A login screen that offers login via email/password.
*/
@ -34,10 +39,11 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
AccountRepository accountRepository;
// UI references.
@BindView(R.id.jid)
private AutoCompleteTextView mJidView;
@BindView(R.id.password)
private EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;
private LoginViewModel viewModel;
@ -49,8 +55,7 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
MercuryImApplication.getApplication().getAppComponent().inject(this);
// Set up the login form.
mJidView = findViewById(R.id.jid);
mPasswordView = findViewById(R.id.password);
ButterKnife.bind(this);
viewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
displayCredentials(viewModel.getAccount());
@ -65,9 +70,6 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
loginDetailsEntered();
}
});
mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
}
private void displayCredentials(LiveData<? extends AccountModel> account) {
@ -86,6 +88,7 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
});
}
// TODO: Move to ViewModel
private void loginDetailsEntered() {
boolean loginIntact = true;
String jidInput = mJidView.getText().toString();
@ -112,8 +115,8 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
accountModel.setJid(jid);
accountModel.setPassword(password);
long id = accountRepository.insertAccount(accountModel);
Log.d(MercuryImApplication.TAG, "LoginActivity.loginDetailsEntered: Account " + id + " inserted.");
attemptLogin(jid, password, id);
}
}

View File

@ -0,0 +1,30 @@
package org.mercury_im.messenger.ui.roster;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.repository.IContactRepository;
import javax.inject.Inject;
public class RosterItemViewModel extends AndroidViewModel {
@Inject
IContactRepository contactRepository;
private LiveData<RoomContactModel> contact;
@Inject
public RosterItemViewModel(@NonNull Application application) {
super(application);
}
public RosterItemViewModel setContactId(long id) {
this.contact = contactRepository.getRosterEntry(id);
return this;
}
}

View File

@ -12,8 +12,11 @@ import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
public class RosterRecyclerViewAdapter
extends RecyclerView.Adapter<RosterRecyclerViewAdapter.RecyclerViewHolder> {
extends RecyclerView.Adapter<RosterRecyclerViewAdapter.AbstractRosterItem> {
private List<RoomContactModel> entryModelList;
@ -23,17 +26,28 @@ public class RosterRecyclerViewAdapter
@NonNull
@Override
public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new RecyclerViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_view_item_roster_entry, parent, false));
public AbstractRosterItem onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// switch (viewType) {
// case RosterItemViewHolder.TYPE:
return new RosterItemViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_view_item_roster_entry, parent, false));
// default:
// return new RosterItemViewHolder(LayoutInflater.from(parent.getContext())
// .inflate(R.layout.recycler_view_item_roster_entry, parent, false));
// }
}
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
public void onBindViewHolder(@NonNull AbstractRosterItem holder, int position) {
RoomContactModel model = entryModelList.get(position);
holder.jidView.setText(model.getJid().toString());
holder.nicknameView.setText(model.getNickname());
holder.itemView.setTag(model);
// switch (holder.getItemViewType()) {
// case RosterItemViewHolder.TYPE:
RosterItemViewHolder rosterItem = (RosterItemViewHolder) holder;
rosterItem.jidView.setText(model.getJid().toString());
rosterItem.nicknameView.setText(model.getNickname());
rosterItem.itemView.setTag(model);
// break;
// }
}
@Override
@ -46,15 +60,31 @@ public class RosterRecyclerViewAdapter
notifyDataSetChanged();
}
public class RecyclerViewHolder extends RecyclerView.ViewHolder {
public abstract class AbstractRosterItem extends RecyclerView.ViewHolder {
private TextView jidView;
private TextView nicknameView;
public RecyclerViewHolder(View itemView) {
public AbstractRosterItem(@NonNull View itemView) {
super(itemView);
this.jidView = itemView.findViewById(R.id.roster_entry__jid);
this.nicknameView = itemView.findViewById(R.id.roster_entry__nickname);
}
abstract void bind(RosterViewModel viewModel);
}
public class RosterItemViewHolder extends AbstractRosterItem {
@BindView(R.id.roster_entry__jid)
TextView jidView;
@BindView(R.id.roster_entry__nickname)
TextView nicknameView;
public RosterItemViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(itemView);
}
@Override
void bind(RosterViewModel viewModel) {
// viewModel.getRosterEntryList().observe();
}
}
}

View File

@ -0,0 +1,9 @@
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment"
android:name="org.mercury_im.messenger.ui.login.AccountsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:layout="@layout/fragment_account_list" />

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_layout_jid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp">
<EditText
android:id="@+id/input_jid"
android:inputType="textEmailAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_jid"
tools:text="alice@wonderland.lit" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_layout_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp">
<EditText
android:id="@+id/input_password"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_password"
tools:text="swordfish"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list"
android:name="org.mercury_im.messenger.ui.login.AccountsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context="org.mercury_im.messenger.ui.login.AccountsFragment"
tools:listitem="@layout/fragment_account" />

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="66dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatar_account"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/aldrin" />
<TextView
android:id="@+id/text_account_jid"
style="@style/TextAppearance.AppCompat.Large"
android:layout_width="291dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/switch_account_enabled"
app:layout_constraintStart_toEndOf="@+id/avatar_account"
app:layout_constraintTop_toTopOf="parent"
tools:text="buzz@jabber.nasa.gov" />
<Switch
android:id="@+id/switch_account_enabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,56 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="org.mercury_im.messenger.ui.roster.RosterViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/roster_entry__avatar"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/aldrin"
tools:src="@drawable/aldrin"
tools:srcCompat="@drawable/aldrin" />
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/roster_entry__avatar"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/aldrin"
tools:src="@drawable/aldrin"
tools:srcCompat="@drawable/aldrin" />
<TextView
android:id="@+id/roster_entry__nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@tools:sample/full_names"
android:textSize="20sp"
android:textColor="@android:color/black"
app:layout_constraintTop_toTopOf="@+id/roster_entry__avatar"
app:layout_constraintStart_toEndOf="@+id/roster_entry__avatar"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/roster_entry__jid" />
<TextView
android:id="@+id/roster_entry__nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewmodel.rosterEntryList."
tools:text="@tools:sample/full_names"
android:textSize="20sp"
android:textColor="@android:color/black"
app:layout_constraintTop_toTopOf="@+id/roster_entry__avatar"
app:layout_constraintStart_toEndOf="@+id/roster_entry__avatar"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/roster_entry__jid" />
<TextView
android:id="@+id/roster_entry__jid"
android:layout_width="285dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="@+id/roster_entry__avatar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.050"
app:layout_constraintStart_toEndOf="@+id/roster_entry__avatar"
tools:text="@tools:sample/lorem[4:10]" />
<TextView
android:id="@+id/roster_entry__jid"
android:layout_width="285dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="@+id/roster_entry__avatar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.050"
app:layout_constraintStart_toEndOf="@+id/roster_entry__avatar"
tools:text="@tools:sample/lorem[4:10]" />
<TextView
android:id="@+id/roster_entry__date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/roster_entry__avatar"
tools:text="@tools:sample/date/hhmm" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/roster_entry__date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/roster_entry__avatar"
tools:text="@tools:sample/date/hhmm" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -19,4 +19,10 @@
android:orderInCategory="120"
android:title="Chat"
app:showAsAction="never" />
<item
android:id="@+id/action_accounts"
android:orderInCategory="130"
android:title="Accounts"
app:showAsAction="never" />
</menu>

View File

@ -3,4 +3,5 @@
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="text_margin">16dp</dimen>
</resources>

View File

@ -42,8 +42,8 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation "androidx.test:core:1.2.0-beta01"
androidTestImplementation 'androidx.test.ext:junit:1.1.1-beta01'
androidTestImplementation "androidx.test:core:1.2.0"
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
// Room
api "androidx.room:room-runtime:$roomVersion"

View File

@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "378204e8bb2f9f4c8bf432291835718c",
"identityHash": "8cbb04481870b8e1320677566696f9ad",
"entities": [
{
"tableName": "RoomContactModel",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `jid` TEXT NOT NULL, `rosterName` TEXT, `nickname` TEXT, PRIMARY KEY(`accountId`, `jid`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `jid` TEXT NOT NULL, `rosterName` TEXT, `nickname` TEXT, `avatarFile` TEXT, PRIMARY KEY(`accountId`, `jid`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`, `jid`) REFERENCES `entities`(`accountId`, `jid`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
"fields": [
{
"fieldPath": "accountId",
@ -31,6 +31,12 @@
"columnName": "nickname",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "avatarFile",
"columnName": "avatarFile",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
@ -62,6 +68,19 @@
"referencedColumns": [
"id"
]
},
{
"table": "entities",
"onDelete": "RESTRICT",
"onUpdate": "NO ACTION",
"columns": [
"accountId",
"jid"
],
"referencedColumns": [
"accountId",
"jid"
]
}
]
},
@ -100,7 +119,16 @@
],
"autoGenerate": true
},
"indices": [],
"indices": [
{
"name": "index_accounts_id",
"unique": false,
"columnNames": [
"id"
],
"createSql": "CREATE INDEX `index_accounts_id` ON `${TABLE_NAME}` (`id`)"
}
],
"foreignKeys": []
},
{
@ -150,7 +178,71 @@
],
"autoGenerate": true
},
"indices": [],
"indices": [
{
"name": "index_messages_id",
"unique": false,
"columnNames": [
"id"
],
"createSql": "CREATE INDEX `index_messages_id` ON `${TABLE_NAME}` (`id`)"
}
],
"foreignKeys": [
{
"table": "accounts",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"accountId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "entities",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `jid` TEXT NOT NULL, `avatarFile` TEXT, PRIMARY KEY(`accountId`, `jid`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "accountId",
"columnName": "accountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "jid",
"columnName": "jid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "avatarFile",
"columnName": "avatarFile",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"accountId",
"jid"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_entities_accountId_jid",
"unique": true,
"columnNames": [
"accountId",
"jid"
],
"createSql": "CREATE UNIQUE INDEX `index_entities_accountId_jid` ON `${TABLE_NAME}` (`accountId`, `jid`)"
}
],
"foreignKeys": [
{
"table": "accounts",
@ -169,7 +261,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"378204e8bb2f9f4c8bf432291835718c\")"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"8cbb04481870b8e1320677566696f9ad\")"
]
}
}

View File

@ -12,8 +12,14 @@ import org.mercury_im.messenger.persistence.room.dao.ContactDao;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.model.RoomMessageModel;
import org.mercury_im.messenger.persistence.room.model.RoomXmppIdentityModel;
@Database(entities = {RoomContactModel.class, RoomAccountModel.class, RoomMessageModel.class}, version = 1)
@Database(entities = {
RoomContactModel.class,
RoomAccountModel.class,
RoomMessageModel.class,
RoomXmppIdentityModel.class},
version = 1)
public abstract class AppDatabase extends RoomDatabase {
private static final String DB_NAME = "mercury_db";

View File

@ -5,20 +5,22 @@ import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.mercury_im.messenger.persistence.room.model.RoomMessageModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import org.mercury_im.messenger.persistence.room.type_converter.EntityFullJidConverter;
import java.util.List;
@Dao
@TypeConverters(EntityFullJidConverter.class)
@TypeConverters(EntityBareJidConverter.class)
public interface MessageDao extends BaseDao<RoomMessageModel> {
@Query("SELECT * FROM messages WHERE accountId=:accountId ORDER BY sendDate DESC")
LiveData<List<RoomMessageModel>> getAllMessagesOf(long accountId);
@Query("SELECT * FROM messages WHERE accountId=:accountId AND `from`=:sender ORDER BY sendDate DESC")
LiveData<List<RoomMessageModel>> getAllMessagesFrom(long accountId, EntityFullJid sender);
LiveData<List<RoomMessageModel>> getAllMessagesFrom(long accountId, EntityBareJid sender);
}

View File

@ -3,6 +3,7 @@ package org.mercury_im.messenger.persistence.room.model;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;
@ -10,7 +11,7 @@ import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
@Entity(tableName = "accounts")
@Entity(tableName = "accounts", indices = {@Index("id")})
public class RoomAccountModel implements AccountModel {
@PrimaryKey(autoGenerate = true)

View File

@ -1,23 +1,30 @@
package org.mercury_im.messenger.persistence.room.model;
import androidx.lifecycle.LiveData;
import androidx.paging.PagedList;
import androidx.room.Entity;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.ChatModel;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.MessageModel;
@Entity
public class RoomChatModel implements ChatModel {
@Override
public LiveData<ContactModel> getContact() {
public EntityBareJid getJid() {
return null;
}
@Override
public LiveData<PagedList<MessageModel>> getMessages() {
return null;
public void setJid(EntityBareJid jid) {
}
@Override
public long getAccountId() {
return 0;
}
@Override
public void setAccountId() {
}
}

View File

@ -10,6 +10,7 @@ import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import org.mercury_im.messenger.persistence.room.type_converter.FileConverter;
import java.io.File;
@ -39,6 +40,7 @@ public class RoomContactModel implements ContactModel {
private String nickname;
@TypeConverters(FileConverter.class)
private File avatarFile;
public RoomContactModel(@NonNull EntityBareJid jid,
@ -90,7 +92,6 @@ public class RoomContactModel implements ContactModel {
this.accountId = accountId;
}
@Override
public File getAvatarFile() {
return avatarFile;
}

View File

@ -2,21 +2,22 @@ package org.mercury_im.messenger.persistence.room.model;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverter;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.MessageModel;
import org.mercury_im.messenger.persistence.room.type_converter.DateConverter;
import org.mercury_im.messenger.persistence.room.type_converter.EntityFullJidConverter;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import java.util.Date;
import static androidx.room.ForeignKey.CASCADE;
@Entity(tableName = "messages", foreignKeys = @ForeignKey(entity = RoomAccountModel.class,
parentColumns = "id", childColumns = "accountId", onDelete = CASCADE))
parentColumns = "id", childColumns = "accountId", onDelete = CASCADE),
indices = {@Index("id")})
public class RoomMessageModel implements MessageModel {
@PrimaryKey(autoGenerate = true)
@ -29,11 +30,11 @@ public class RoomMessageModel implements MessageModel {
@TypeConverters(DateConverter.class)
private Date sendDate;
@TypeConverters(EntityFullJidConverter.class)
private EntityFullJid from;
@TypeConverters(EntityBareJidConverter.class)
private EntityBareJid from;
@TypeConverters(EntityFullJidConverter.class)
private EntityFullJid to;
@TypeConverters(EntityBareJidConverter.class)
private EntityBareJid to;
public RoomMessageModel() {
@ -80,22 +81,22 @@ public class RoomMessageModel implements MessageModel {
}
@Override
public EntityFullJid getFrom() {
public EntityBareJid getFrom() {
return from;
}
@Override
public void setFrom(EntityFullJid sender) {
public void setFrom(EntityBareJid sender) {
this.from = sender;
}
@Override
public EntityFullJid getTo() {
public EntityBareJid getTo() {
return to;
}
@Override
public void setTo(EntityFullJid recipient) {
public void setTo(EntityBareJid recipient) {
this.to = recipient;
}
}

View File

@ -6,9 +6,12 @@ import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.XmppIdentityModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import org.mercury_im.messenger.persistence.room.type_converter.FileConverter;
import java.io.File;
@ -24,8 +27,11 @@ public class RoomXmppIdentityModel implements XmppIdentityModel {
protected long accountId;
@NonNull
@TypeConverters(EntityBareJidConverter.class)
protected EntityBareJid jid;
@TypeConverters(FileConverter.class)
protected File avatarFile;
@NonNull

View File

@ -1,7 +1,27 @@
package org.mercury_im.messenger.persistence.room.repository;
import androidx.lifecycle.LiveData;
import org.mercury_im.messenger.persistence.model.ChatModel;
import org.mercury_im.messenger.persistence.model.XmppIdentityModel;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import java.util.List;
public class IChatRepository implements ChatRepository {
@Override
public LiveData<List<ChatModel>> getAllChats() {
return null;
}
@Override
public void getChatWith(XmppIdentityModel identity) {
}
@Override
public void closeChat(ChatModel chat) {
}
}

View File

@ -26,5 +26,10 @@ public class IContactRepository implements ContactRepository<RoomContactModel> {
contactDao.insert(rosterEntryModel);
}
@Override
public LiveData<RoomContactModel> getRosterEntry(long id) {
return null;
}
}

View File

@ -29,7 +29,12 @@ public class IMessageRepository implements MessageRepository<RoomMessageModel> {
}
@Override
public LiveData<List<RoomMessageModel>> getAllMessagesFrom(long accountId, EntityFullJid contact) {
public LiveData<List<RoomMessageModel>> getAllMessagesFrom(long accountId, EntityBareJid contact) {
return messageDao.getAllMessagesFrom(accountId, contact);
}
@Override
public LiveData<List<RoomMessageModel>> findMessageByQuery(String query) {
return null;
}
}

View File

@ -0,0 +1,18 @@
package org.mercury_im.messenger.persistence.room.type_converter;
import androidx.room.TypeConverter;
import java.io.File;
public class FileConverter {
@TypeConverter
public static String toString(File file) {
return file.getAbsolutePath();
}
@TypeConverter
public static File toFile(String string) {
return new File(string);
}
}

View File

@ -2,6 +2,8 @@ package org.mercury_im.messenger.persistence.model;
import org.jxmpp.jid.EntityBareJid;
import java.io.File;
public interface ContactModel {
long getAccountId();
@ -19,4 +21,8 @@ public interface ContactModel {
String getNickname();
void setNickname(String nickname);
File getAvatarFile();
void setAvatarFile(File file);
}

View File

@ -11,4 +11,6 @@ public interface ContactRepository<E extends ContactModel> {
LiveData<List<E>> getAllRosterEntries();
void updateOrInsertRosterEntry(E rosterEntryModel);
LiveData<E> getRosterEntry(long id);
}

View File

@ -37,7 +37,7 @@ ext {
// Architecture Components
lifecycleVersion = "2.0.0"
roomVersion = "2.1.0-beta01"
roomVersion = "2.1.0-rc01"
pagingVersion = "2.1.0"
// Dagger 2
@ -45,4 +45,7 @@ ext {
// Android Support Library
supportLibVersion = "28.0.0"
// Butter Knife
butterKnifeVersion = "10.1.0"
}