Primitive XMPP connection

This commit is contained in:
Paul Schaub 2018-08-01 11:43:18 +02:00
parent 8e51695e69
commit 7bea4d5308
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
30 changed files with 575 additions and 40 deletions

Binary file not shown.

View File

@ -0,0 +1,29 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
</code_scheme>
</component>

View File

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

View File

@ -8,12 +8,12 @@ buildscript {
} }
ext { ext {
smackVersion="4.2.3-SNAPSHOT" smackVersion="4.4.0-alpha2-SNAPSHOT"
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.1' classpath 'com.android.tools.build:gradle:3.1.3'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }

View File

@ -1,6 +1,6 @@
#Mon Jan 22 17:04:57 CET 2018 #Wed Aug 01 11:38:19 CEST 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

View File

@ -29,18 +29,24 @@ dependencies {
implementation 'com.android.support:support-emoji:27.0.2' implementation 'com.android.support:support-emoji:27.0.2'
implementation 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.android.support.constraint:constraint-layout:1.0.2'
// The holy ButterKnife \o/
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
testImplementation 'junit:junit:4.12' // External UI
androidTestImplementation 'com.android.support.test:runner:1.0.1' compile 'de.hdodenhof:circleimageview:2.0.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
// Smack
compile "org.igniterealtime.smack:smack-android-extensions:$smackVersion" compile "org.igniterealtime.smack:smack-android-extensions:$smackVersion"
compile "org.igniterealtime.smack:smack-omemo-signal:$smackVersion" compile "org.igniterealtime.smack:smack-omemo-signal:$smackVersion"
compile "org.igniterealtime.smack:smack-tcp:$smackVersion" compile "org.igniterealtime.smack:smack-tcp:$smackVersion"
compile "org.igniterealtime.smack:smack-experimental:$smackVersion" compile "org.igniterealtime.smack:smack-experimental:$smackVersion"
compile 'com.jakewharton:butterknife:8.8.1' // SQLCipher
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' compile 'net.zetetic:android-database-sqlcipher:3.5.9@aar'
compile 'de.hdodenhof:circleimageview:2.0.0' // Test stuff
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
} }

View File

@ -34,6 +34,7 @@
android:resource="@xml/automotive_app_desc" /> android:resource="@xml/automotive_app_desc" />
<service android:name=".service.MyMessagingService"/> <service android:name=".service.MyMessagingService"/>
<service android:name=".service.SlamXmppService" />
<receiver android:name=".receiver.MessageReadReceiver"> <receiver android:name=".receiver.MessageReadReceiver">
<intent-filter> <intent-filter>

View File

@ -18,14 +18,16 @@
package de.vanitasvitae.slam; package de.vanitasvitae.slam;
import android.app.Application; import android.app.Application;
import android.content.Intent;
import de.vanitasvitae.slam.mvp.DummyPresenterFactory; import de.vanitasvitae.slam.mvp.DummyPresenterFactory;
import de.vanitasvitae.slam.mvp.PresenterFactory; import de.vanitasvitae.slam.mvp.PresenterFactory;
import de.vanitasvitae.slam.service.SlamXmppService;
public class SlamApplication extends Application { public class SlamApplication extends Application {
public SlamApplication() { public SlamApplication() {
super(); super();
PresenterFactory.setInstance(new DummyPresenterFactory()); PresenterFactory.setInstance(new PresenterFactory());
} }
} }

View File

@ -0,0 +1,32 @@
/*
* Copyright 2018 Paul Schaub
*
* This code is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* 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.mvp.contracts;
/**
* Created by Paul Schaub on 24.02.18.
*/
public interface BaseContract {
interface BaseView<T extends BasePresenter> {
void setPresenter(T presenter);
}
interface BasePresenter {
}
}

View File

@ -26,7 +26,7 @@ import de.vanitasvitae.slam.xmpp.Resource;
*/ */
public interface ContactDetailContract { public interface ContactDetailContract {
interface View { interface View extends BaseContract.BaseView<Presenter> {
void setContactAvatar(); void setContactAvatar();
void setNickname(String nickname); void setNickname(String nickname);
void setResources(List<Resource> presences); void setResources(List<Resource> presences);
@ -34,7 +34,7 @@ public interface ContactDetailContract {
void addFingerprints(List<?> fingerprints); void addFingerprints(List<?> fingerprints);
} }
interface Presenter { interface Presenter extends BaseContract.BasePresenter {
void onAvatarClick(); void onAvatarClick();
void onSharedMediaClick(); void onSharedMediaClick();
void onAudioCallClick(); void onAudioCallClick();

View File

@ -31,7 +31,7 @@ import de.vanitasvitae.slam.xmpp.Contact;
*/ */
public interface ContactListContract { public interface ContactListContract {
interface View { interface View extends BaseContract.BaseView<Presenter> {
void addContactListItems(List<Contact> contacts); void addContactListItems(List<Contact> contacts);
void clearContactListItems(); void clearContactListItems();
void onUpdateContactPresence(); void onUpdateContactPresence();
@ -41,7 +41,7 @@ public interface ContactListContract {
void navigateToContactDetail(BareJid contact); void navigateToContactDetail(BareJid contact);
} }
interface Presenter { interface Presenter extends BaseContract.BasePresenter {
void onContactListItemClick(); void onContactListItemClick();
void onContactListItemLongClick(); void onContactListItemLongClick();
void addNewContact(); void addNewContact();

View File

@ -30,14 +30,14 @@ import de.vanitasvitae.slam.xmpp.message.AbstractMessage;
*/ */
public interface ConversationContract { public interface ConversationContract {
interface View { interface View extends BaseContract.BaseView<Presenter> {
void addMessageItems(List<AbstractMessage> messages, boolean end); void addMessageItems(List<AbstractMessage> messages, boolean end);
void highlightMessageItem(); void highlightMessageItem();
void correctMessageItem(); void correctMessageItem();
void navigateToContactProfile(); void navigateToContactProfile();
} }
interface Presenter { interface Presenter extends BaseContract.BasePresenter {
void setPeersJid(EntityBareJid jid); void setPeersJid(EntityBareJid jid);
void onConversationScrolledToTop(); void onConversationScrolledToTop();
void onComposingMessageChanged(String composingMessage); void onComposingMessageChanged(String composingMessage);

View File

@ -29,13 +29,13 @@ import de.vanitasvitae.slam.xmpp.Conversation;
*/ */
public interface ConversationListContract { public interface ConversationListContract {
interface View { interface View extends BaseContract.BaseView<Presenter> {
void populateConversationList(List<Conversation> conversations); void populateConversationList(List<Conversation> conversations);
void navigateToConversation(BareJid contact); void navigateToConversation(BareJid contact);
void navigateToContactDetail(BareJid contact); void navigateToContactDetail(BareJid contact);
} }
interface Presenter { interface Presenter extends BaseContract.BasePresenter {
void onConversationClick(); void onConversationClick();
void onConversationLongClick(); void onConversationLongClick();
void load(); void load();

View File

@ -17,13 +17,15 @@
*/ */
package de.vanitasvitae.slam.mvp.contracts; package de.vanitasvitae.slam.mvp.contracts;
import de.vanitasvitae.slam.service.SlamXmppService;
/** /**
* Model-View-Presenter contract of the login screen. * Model-View-Presenter contract of the login screen.
* Created by Paul Schaub on 01.02.18. * Created by Paul Schaub on 01.02.18.
*/ */
public interface LoginContract { public interface LoginContract {
interface View { interface View extends BaseContract.BaseView<Presenter> {
void showInvalidJidError(); void showInvalidJidError();
void hideInvalidJidError(); void hideInvalidJidError();
void showInvalidPasswordError(); void showInvalidPasswordError();
@ -33,11 +35,15 @@ public interface LoginContract {
void showProgressIndicator(); void showProgressIndicator();
void hideProgressIndicator(); void hideProgressIndicator();
void navigateToMainActivity(); void navigateToMainActivity();
void disableLoginButton();
void enableLoginButton();
} }
interface Presenter { interface Presenter extends BaseContract.BasePresenter {
void jidChanged(String jid); void jidChanged(String jid);
void passwordChanged(String password); void passwordChanged(String password);
void loginClicked(); void loginClicked();
void bindService(SlamXmppService service);
void unbindService();
} }
} }

View File

@ -25,7 +25,7 @@ import java.util.List;
*/ */
public interface SearchContract { public interface SearchContract {
interface View { interface View extends BaseContract.BaseView<Presenter> {
void addSearchResults(List<?> results); void addSearchResults(List<?> results);
void clearSearchResults(); void clearSearchResults();
void showLoadingIndicator(); void showLoadingIndicator();
@ -34,7 +34,7 @@ public interface SearchContract {
void hideEmptySearchResults(); void hideEmptySearchResults();
} }
interface Presenter { interface Presenter extends BaseContract.BasePresenter {
void onSearchQueryChanged(String query); void onSearchQueryChanged(String query);
void onSearchResultClick(); void onSearchResultClick();
void onSearchScrolledToBottom(); void onSearchScrolledToBottom();

View File

@ -17,12 +17,14 @@
*/ */
package de.vanitasvitae.slam.mvp.contracts.message; package de.vanitasvitae.slam.mvp.contracts.message;
import de.vanitasvitae.slam.mvp.contracts.BaseContract;
/** /**
* Model-View-Presenter contract for an abstract message. * Model-View-Presenter contract for an abstract message.
*/ */
public interface AbstractMessageContract { public interface AbstractMessageContract {
interface View { interface View extends BaseContract.BaseView<Presenter> {
void setDirection(Direction direction); void setDirection(Direction direction);
void setStatusSending(); void setStatusSending();
void setStatusSendingFailed(); void setStatusSendingFailed();
@ -33,7 +35,7 @@ public interface AbstractMessageContract {
void displayErrorMessage(); void displayErrorMessage();
} }
interface Presenter { interface Presenter extends BaseContract.BasePresenter {
void onDeleteMessage(); void onDeleteMessage();
void onReadMessage(); void onReadMessage();
void onMessageClick(); void onMessageClick();

View File

@ -17,18 +17,38 @@
*/ */
package de.vanitasvitae.slam.mvp.presenter; package de.vanitasvitae.slam.mvp.presenter;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
import org.jivesoftware.smack.packet.Message;
import org.jxmpp.jid.BareJid; import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
import java.io.IOException;
import java.net.UnknownHostException;
import de.vanitasvitae.slam.mvp.contracts.LoginContract; import de.vanitasvitae.slam.mvp.contracts.LoginContract;
import de.vanitasvitae.slam.service.SlamXmppConnection;
import de.vanitasvitae.slam.service.SlamXmppService;
import de.vanitasvitae.slam.xmpp.Account;
public class LoginPresenter implements LoginContract.Presenter { public class LoginPresenter implements LoginContract.Presenter {
private static final String TAG = "LoginPresenter";
private final LoginContract.View view; private final LoginContract.View view;
private BareJid jid; private BareJid jid;
private String password; private String password;
private SlamXmppService service;
public LoginPresenter(LoginContract.View view) { public LoginPresenter(LoginContract.View view) {
this.view = view; this.view = view;
@ -39,19 +59,115 @@ public class LoginPresenter implements LoginContract.Presenter {
try { try {
this.jid = JidCreate.entityBareFrom(jid); this.jid = JidCreate.entityBareFrom(jid);
view.hideInvalidJidError(); view.hideInvalidJidError();
if (password != null && !password.isEmpty()) {
view.enableLoginButton();
}
} catch (XmppStringprepException e) { } catch (XmppStringprepException e) {
this.jid = null; this.jid = null;
view.showInvalidJidError(); view.showInvalidJidError();
view.disableLoginButton();
} }
} }
@Override @Override
public void passwordChanged(String password) { public void passwordChanged(String password) {
this.password = password;
if (password == null || password.isEmpty()) {
view.disableLoginButton();
} else if (jid != null) {
view.enableLoginButton();
}
} }
@Override @Override
public void loginClicked() { public void loginClicked() {
if (jid == null) {
view.showInvalidJidError();
return;
}
if (password == null) {
view.showInvalidPasswordError();
return;
}
if (service != null) {
view.disableLoginButton();
final Account account = new Account(jid);
Thread loginThread = new Thread(new Runnable() {
@Override
public void run() {
SlamXmppConnection connection = null;
try {
connection = new SlamXmppConnection(
jid.asEntityBareJidOrThrow(), password);
} catch (UnknownHostException e) {
e.printStackTrace();
((AppCompatActivity)view).runOnUiThread(new Runnable() {
public void run() {
Toast.makeText((Context)view, "Unknown host", Toast.LENGTH_SHORT).show();
}
});
return;
}
service.addConnection(account, connection);
final SlamXmppConnection finalConnection = connection;
connection.login(new SlamXmppConnection.LoginCallback() {
@Override
public void success() {
((AppCompatActivity)view).runOnUiThread(new Runnable() {
public void run() {
view.navigateToMainActivity();
}
});
ChatManager.getInstanceFor(finalConnection.getConnection())
.addIncomingListener(new IncomingChatMessageListener() {
@Override
public void newIncomingMessage(final EntityBareJid from, final Message message, Chat chat) {
final AppCompatActivity activity = (AppCompatActivity) view;
activity.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(activity.getApplicationContext(), from.toString() + ": " + message.getBody(), Toast.LENGTH_LONG).show();
}
});
}
});
}
@Override
public void failure(final Exception e) {
e.printStackTrace();
((AppCompatActivity)view).runOnUiThread(new Runnable() {
public void run() {
Toast.makeText((Context)view, e.getMessage(), Toast.LENGTH_LONG).show();
view.enableLoginButton();
}
});
}
});
}
});
loginThread.start();
} else {
view.showServerNotFoundError();
}
}
@Override
public void bindService(SlamXmppService service) {
Log.d(TAG, "bindService()");
this.service = service;
}
@Override
public void unbindService() {
Log.d(TAG, "unbindService()");
this.service = null;
} }
} }

View File

@ -27,12 +27,13 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.vanitasvitae.slam.mvp.contracts.ConversationContract; import de.vanitasvitae.slam.mvp.contracts.ConversationContract;
import de.vanitasvitae.slam.xmpp.message.AbstractMessage;
public class DummyConversationPresenter implements ConversationContract.Presenter { public class DummyConversationPresenter implements ConversationContract.Presenter {
private final ConversationContract.View view; private final ConversationContract.View view;
private final List<Message> dummyMessages = new ArrayList<>(); private final List<AbstractMessage> dummyMessages = new ArrayList<>();
public DummyConversationPresenter(ConversationContract.View view) { public DummyConversationPresenter(ConversationContract.View view) {
this.view = view; this.view = view;
@ -41,6 +42,7 @@ public class DummyConversationPresenter implements ConversationContract.Presente
} }
private void populateDummyMessages() { private void populateDummyMessages() {
/*
try { try {
BareJid alice = JidCreate.bareFrom("alice@wonderland.lit"); BareJid alice = JidCreate.bareFrom("alice@wonderland.lit");
@ -66,6 +68,7 @@ public class DummyConversationPresenter implements ConversationContract.Presente
} catch (XmppStringprepException e) { } catch (XmppStringprepException e) {
e.printStackTrace(); e.printStackTrace();
} }
*/
} }
@Override @Override

View File

@ -23,6 +23,7 @@ import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
import de.vanitasvitae.slam.mvp.contracts.LoginContract; import de.vanitasvitae.slam.mvp.contracts.LoginContract;
import de.vanitasvitae.slam.service.SlamXmppService;
/** /**
* Dummy presenter, that has no model. * Dummy presenter, that has no model.
@ -82,4 +83,14 @@ public class DummyLoginPresenter implements LoginContract.Presenter {
} }
}, 250); }, 250);
} }
@Override
public void bindService(SlamXmppService service) {
}
@Override
public void unbindService() {
}
} }

View File

@ -45,7 +45,7 @@ public class ContactDetailActivity extends ThemedAppCompatActivity
public static final String TAG = "Slam!"; public static final String TAG = "Slam!";
private final ContactDetailContract.Presenter presenter; private ContactDetailContract.Presenter presenter;
private final ContactDetailResourcesFragment resourcesFragment = new ContactDetailResourcesFragment(); private final ContactDetailResourcesFragment resourcesFragment = new ContactDetailResourcesFragment();
private final ContactDetailInfoFragment infoFragment = new ContactDetailInfoFragment(); private final ContactDetailInfoFragment infoFragment = new ContactDetailInfoFragment();
@ -71,7 +71,7 @@ public class ContactDetailActivity extends ThemedAppCompatActivity
private int animateProfileCirclePercent = 30; private int animateProfileCirclePercent = 30;
public ContactDetailActivity() { public ContactDetailActivity() {
this.presenter = PresenterFactory.getInstance().createContactDetailPresenter(this); setPresenter(PresenterFactory.getInstance().createContactDetailPresenter(this));
} }
@Override @Override
@ -151,6 +151,11 @@ public class ContactDetailActivity extends ThemedAppCompatActivity
} }
@Override
public void setPresenter(ContactDetailContract.Presenter presenter) {
this.presenter = presenter;
}
class DetailFragmentPagerAdapter extends FragmentPagerAdapter { class DetailFragmentPagerAdapter extends FragmentPagerAdapter {
public DetailFragmentPagerAdapter(FragmentManager fm) { public DetailFragmentPagerAdapter(FragmentManager fm) {

View File

@ -47,13 +47,13 @@ public class ContactListFragment extends Fragment implements ContactListContract
@BindView(R.id.recycler_list) @BindView(R.id.recycler_list)
RecyclerView recyclerView; RecyclerView recyclerView;
private final ContactListContract.Presenter presenter; private ContactListContract.Presenter presenter;
private final List<Contact> contacts = new ArrayList<>(); private final List<Contact> contacts = new ArrayList<>();
public ContactListFragment() { public ContactListFragment() {
super(); super();
this.presenter = PresenterFactory.getInstance().createContactListPresenter(this); setPresenter(PresenterFactory.getInstance().createContactListPresenter(this));
} }
@Override @Override
@ -142,4 +142,8 @@ public class ContactListFragment extends Fragment implements ContactListContract
} }
@Override
public void setPresenter(ContactListContract.Presenter presenter) {
this.presenter = presenter;
}
} }

View File

@ -56,7 +56,7 @@ public class ConversationFragment extends Fragment implements ConversationContra
@BindView(R.id.recycler_chat) @BindView(R.id.recycler_chat)
RecyclerView recyclerView; RecyclerView recyclerView;
private final ConversationContract.Presenter presenter; private ConversationContract.Presenter presenter;
Map<String, Integer> messageIdIndizes = new HashMap<>(); Map<String, Integer> messageIdIndizes = new HashMap<>();
List<AbstractMessage> messages = new ArrayList<>(); List<AbstractMessage> messages = new ArrayList<>();
@ -87,7 +87,7 @@ public class ConversationFragment extends Fragment implements ConversationContra
public ConversationFragment() { public ConversationFragment() {
super(); super();
this.presenter = PresenterFactory.getInstance().createConversationPresenter(this); setPresenter(PresenterFactory.getInstance().createConversationPresenter(this));
} }
@Override @Override
@ -117,6 +117,11 @@ public class ConversationFragment extends Fragment implements ConversationContra
recyclerView.getAdapter().notifyDataSetChanged(); recyclerView.getAdapter().notifyDataSetChanged();
} }
@Override
public void setPresenter(ConversationContract.Presenter presenter) {
this.presenter = presenter;
}
@Override @Override
public void addMessageItems(List<AbstractMessage> messages, boolean end) { public void addMessageItems(List<AbstractMessage> messages, boolean end) {
if (end) { if (end) {

View File

@ -49,12 +49,12 @@ public class ConversationListFragment extends Fragment implements ConversationLi
@BindView(R.id.recycler_list) @BindView(R.id.recycler_list)
RecyclerView recyclerView; RecyclerView recyclerView;
private final ConversationListContract.Presenter presenter; private ConversationListContract.Presenter presenter;
private final List<Conversation> conversations = new ArrayList<>(); private final List<Conversation> conversations = new ArrayList<>();
public ConversationListFragment() { public ConversationListFragment() {
super(); super();
this.presenter = PresenterFactory.getInstance().createConversationListPresenter(this); setPresenter(PresenterFactory.getInstance().createConversationListPresenter(this));
} }
@Override @Override
@ -72,6 +72,11 @@ public class ConversationListFragment extends Fragment implements ConversationLi
recyclerView.getAdapter().notifyDataSetChanged(); recyclerView.getAdapter().notifyDataSetChanged();
} }
@Override
public void setPresenter(ConversationListContract.Presenter presenter) {
this.presenter = presenter;
}
@Override @Override
public void populateConversationList(List<Conversation> conversations) { public void populateConversationList(List<Conversation> conversations) {
this.conversations.clear(); this.conversations.clear();

View File

@ -17,8 +17,12 @@
*/ */
package de.vanitasvitae.slam.mvp.view; package de.vanitasvitae.slam.mvp.view;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import android.support.design.widget.TextInputEditText; import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
@ -40,8 +44,9 @@ import de.vanitasvitae.slam.R;
import de.vanitasvitae.slam.mvp.PresenterFactory; import de.vanitasvitae.slam.mvp.PresenterFactory;
import de.vanitasvitae.slam.mvp.view.abstr.ThemedAppCompatActivity; import de.vanitasvitae.slam.mvp.view.abstr.ThemedAppCompatActivity;
import de.vanitasvitae.slam.mvp.contracts.LoginContract; import de.vanitasvitae.slam.mvp.contracts.LoginContract;
import de.vanitasvitae.slam.service.SlamXmppService;
public class LoginActivity extends ThemedAppCompatActivity implements LoginContract.View { public class LoginActivity extends ThemedAppCompatActivity implements LoginContract.View, ServiceConnection {
// Presenter of this view // Presenter of this view
private LoginContract.Presenter presenter; private LoginContract.Presenter presenter;
@ -67,9 +72,11 @@ public class LoginActivity extends ThemedAppCompatActivity implements LoginContr
@BindView(R.id.button_login) @BindView(R.id.button_login)
Button buttonLogin; Button buttonLogin;
private SlamXmppService slamService;
public LoginActivity() { public LoginActivity() {
super(); super();
this.presenter = PresenterFactory.getInstance().createLoginPresenter(this); setPresenter(PresenterFactory.getInstance().createLoginPresenter(this));
} }
@Override @Override
@ -103,8 +110,29 @@ public class LoginActivity extends ThemedAppCompatActivity implements LoginContr
}); });
} }
@Override
protected void onResume() {
super.onResume();
// Start xmpp service
Intent serviceIntent = new Intent(this, SlamXmppService.class);
bindService(serviceIntent, this, Context.BIND_AUTO_CREATE);
}
@Override
protected void onPause() {
super.onPause();
unbindService(this);
}
@Override
public void setPresenter(LoginContract.Presenter presenter) {
this.presenter = presenter;
}
@OnClick(R.id.button_login) @OnClick(R.id.button_login)
void login() { void login() {
Intent serviceIntent = new Intent(getApplicationContext(), SlamXmppService.class);
getApplicationContext().startService(serviceIntent);
presenter.loginClicked(); presenter.loginClicked();
} }
@ -162,5 +190,28 @@ public class LoginActivity extends ThemedAppCompatActivity implements LoginContr
startActivity(new Intent(this, MainActivity.class)); startActivity(new Intent(this, MainActivity.class));
finish(); finish();
} }
@Override
public void disableLoginButton() {
buttonLogin.setEnabled(false);
}
@Override
public void enableLoginButton() {
buttonLogin.setEnabled(true);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
SlamXmppService.SlamBinder binder = (SlamXmppService.SlamBinder) service;
slamService = binder.getService();
presenter.bindService(slamService);
}
@Override
public void onServiceDisconnected(ComponentName name) {
slamService = null;
presenter.unbindService();
}
} }

View File

@ -31,10 +31,10 @@ import de.vanitasvitae.slam.mvp.contracts.SearchContract;
*/ */
public class SearchFragment extends Fragment implements SearchContract.View { public class SearchFragment extends Fragment implements SearchContract.View {
private final SearchContract.Presenter presenter; private SearchContract.Presenter presenter;
public SearchFragment() { public SearchFragment() {
this.presenter = PresenterFactory.getInstance().createSearchPresenter(this); setPresenter(PresenterFactory.getInstance().createSearchPresenter(this));
} }
@Override @Override
@ -66,4 +66,9 @@ public class SearchFragment extends Fragment implements SearchContract.View {
public void hideEmptySearchResults() { public void hideEmptySearchResults() {
} }
@Override
public void setPresenter(SearchContract.Presenter presenter) {
this.presenter = presenter;
}
} }

View File

@ -34,6 +34,8 @@ import de.vanitasvitae.slam.xmpp.message.AbstractMessage;
public abstract class MessageView<T extends AbstractMessage> extends RecyclerView.ViewHolder public abstract class MessageView<T extends AbstractMessage> extends RecyclerView.ViewHolder
implements AbstractMessageContract.View { implements AbstractMessageContract.View {
protected AbstractMessageContract.Presenter presenter;
public MessageView(View itemView) { public MessageView(View itemView) {
super(itemView); super(itemView);
} }
@ -83,4 +85,9 @@ public abstract class MessageView<T extends AbstractMessage> extends RecyclerVie
public void displayErrorMessage() { public void displayErrorMessage() {
} }
@Override
public void setPresenter(AbstractMessageContract.Presenter presenter) {
this.presenter = presenter;
}
} }

View File

@ -0,0 +1,86 @@
/*
* Copyright 2018 Paul Schaub
*
* This code is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* 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.service;
import android.util.Log;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.StringUtils;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.stringprep.XmppStringprepException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Created by Paul Schaub on 24.02.18.
*/
public class SlamXmppConnection {
private static final String TAG = "SlamConn";
private final EntityBareJid jid;
private final AbstractXMPPConnection connection;
private final Resourcepart resourcepart;
public SlamXmppConnection(EntityBareJid jid, String password) throws UnknownHostException {
this.jid = jid;
// Resource
String res = "Slam-" + StringUtils.randomString(12);
try {
this.resourcepart = Resourcepart.from(res);
} catch (XmppStringprepException e) {
throw new AssertionError("Resourcepart \"" + res + "\" appears to be invalid.", e);
}
// Configuration
XMPPTCPConnectionConfiguration configuration = XMPPTCPConnectionConfiguration.builder()
.setHost(this.jid.getDomain().toString())
.setHostAddress(InetAddress.getByName(jid.getDomain().toString()))
.setXmppDomain(jid.asDomainBareJid())
.setUsernameAndPassword(jid.getLocalpart().toString(), password)
.setResource(resourcepart)
.build();
this.connection = new XMPPTCPConnection(configuration);
}
public AbstractXMPPConnection getConnection() {
return connection;
}
public void login(final LoginCallback callback) {
try {
getConnection().connect().login();
Log.d(TAG, "Account " + jid.toString() + " logged in.");
callback.success();
} catch (Exception e) {
Log.d(TAG, "Account " + jid.toString() + " could not log in.");
e.printStackTrace();
callback.failure(e);
}
}
public interface LoginCallback {
void success();
void failure(Exception e);
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2018 Paul Schaub
*
* This code is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* 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.service;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.HashMap;
import de.vanitasvitae.slam.xmpp.Account;
/**
* Created by Paul Schaub on 24.02.18.
*/
public class SlamXmppService extends Service {
private static final String TAG = "SlamService";
private final IBinder binder = new SlamBinder();
private Account currentAccount;
private final HashMap<Account, SlamXmppConnection> connections = new HashMap<>();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG,"onStartCommand()");
return Service.START_STICKY;
}
public void addConnection(@NonNull Account account, SlamXmppConnection connection) {
Log.d(TAG, "Adding a new connection for " + account.getJid().toString());
this.connections.put(account, connection);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy()");
for (SlamXmppConnection c : connections.values()) {
c.getConnection().disconnect();
}
super.onDestroy();
}
public SlamXmppConnection getConnection(@NonNull Account account) {
return connections.get(account);
}
public class SlamBinder extends Binder {
public SlamXmppService getService() {
return SlamXmppService.this;
}
}
}

View File

@ -0,0 +1,69 @@
package de.vanitasvitae.slam.task;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import org.jxmpp.jid.EntityBareJid;
import java.net.InetAddress;
import java.net.UnknownHostException;
import de.vanitasvitae.slam.mvp.contracts.LoginContract;
import de.vanitasvitae.slam.service.SlamXmppConnection;
/**
* Created by Paul Schaub on 26.02.18.
*/
public class LoginTask extends AsyncTask<LoginTask.Credentials, Void, SlamXmppConnection> {
private final LoginContract.View view;
private Exception exception;
public LoginTask(LoginContract.View view) {
this.view = view;
}
@Override
protected SlamXmppConnection doInBackground(Credentials... credentials) {
if (credentials.length != 1) {
throw new IllegalArgumentException("Please only present one set of credentials at a time.");
}
SlamXmppConnection connection;
try {
connection = new SlamXmppConnection(credentials[0].jid, credentials[0].password);
} catch (UnknownHostException e) {
exception = e;
return null;
}
return null;
}
@Override
protected void onPreExecute() {
}
@Override
protected void onPostExecute(SlamXmppConnection result) {
if (exception == null) {
return;
}
if (exception instanceof UnknownHostException) {
view.showServerNotFoundError();
return;
}
}
public static class Credentials {
private final EntityBareJid jid;
private final String password;
public Credentials(@NonNull EntityBareJid jid, @NonNull String password) {
this.jid = jid;
this.password = password;
}
}
}

View File

@ -17,8 +17,20 @@
*/ */
package de.vanitasvitae.slam.xmpp; package de.vanitasvitae.slam.xmpp;
import org.jxmpp.jid.BareJid;
/** /**
* Created by Paul Schaub on 24.02.18. * Created by Paul Schaub on 24.02.18.
*/ */
public class Account { public class Account {
private final BareJid jid;
public Account(BareJid jid) {
this.jid = jid;
}
public BareJid getJid() {
return jid;
}
} }