From 2e0623a19fa4dbc79f02de562466d79944ca7bad Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Thu, 28 Jan 2016 21:18:56 +0100 Subject: [PATCH] Added proper backlog filtering, refactored some stuff --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 2 +- .../java/de/kuschku/libquassel/Client.java | 80 +++- .../de/kuschku/libquassel/CoreConnection.java | 64 ++- .../kuschku/libquassel/IProtocolHandler.java | 10 +- .../kuschku/libquassel/ProtocolHandler.java | 42 +- .../events/BacklogReceivedEvent.java | 2 +- .../libquassel/events/GeneralErrorEvent.java | 9 +- .../libquassel/events/LagChangedEvent.java | 16 + .../exceptions/UnknownTypeException.java | 8 +- .../serializers/HeartbeatReplySerializer.java | 46 +++ .../serializers/HeartbeatSerializer.java | 44 +++ .../libquassel/functions/types/Heartbeat.java | 18 + .../functions/types/HeartbeatReply.java | 18 + .../kuschku/libquassel/localtypes/Buffer.java | 2 + .../libquassel/localtypes/ChannelBuffer.java | 11 +- .../localtypes/NotificationManager.java | 18 + .../libquassel/localtypes/QueryBuffer.java | 5 + .../libquassel/localtypes/StatusBuffer.java | 5 + .../backlogmanagers/BacklogFilter.java | 72 ++++ .../backlogmanagers/BacklogManager.java | 14 +- .../backlogmanagers/SimpleBacklogManager.java | 68 +++- .../serializers/CharSerializer.java | 14 +- .../serializers/MessageSerializer.java | 17 +- .../libquassel/protocols/DatastreamPeer.java | 89 ++++- .../libquassel/protocols/LegacyPeer.java | 104 +++-- .../syncables/SyncableRegistry.java | 8 +- .../kuschku/libquassel/syncables/Synced.java | 8 + .../serializers/AliasManagerSerializer.java | 60 +++ .../serializers/BufferSyncerSerializer.java | 13 +- .../BufferViewManagerSerializer.java | 3 +- .../IgnoreListManagerSerializer.java | 63 +++ .../serializers/IrcChannelSerializer.java | 12 +- .../serializers/IrcUserSerializer.java | 32 +- .../serializers/NetworkConfigSerializer.java | 63 +++ .../serializers/NetworkSerializer.java | 13 +- .../syncables/types/AliasManager.java | 38 ++ .../syncables/types/BufferSyncer.java | 25 +- .../syncables/types/BufferViewConfig.java | 58 ++- .../syncables/types/BufferViewManager.java | 22 +- .../libquassel/syncables/types/Identity.java | 16 +- .../syncables/types/IgnoreListManager.java | 150 +++++++ .../syncables/types/IrcChannel.java | 205 +++++++++- .../libquassel/syncables/types/IrcUser.java | 128 +++++- .../libquassel/syncables/types/Network.java | 111 +++++- .../syncables/types/NetworkConfig.java | 120 ++++++ .../syncables/types/SyncableObject.java | 10 +- .../quasseldroid_ng/ui/AppContext.java | 65 +++ .../kuschku/quasseldroid_ng/ui/AppTheme.java | 7 + .../ui/chat/BufferViewConfigWrapper.java | 104 +++++ .../quasseldroid_ng/ui/chat/ChatActivity.java | 373 ++++++++++++++---- .../ui/chat/chatview/ChatMessageRenderer.java | 33 +- .../ui/chat/chatview/MessageAdapter.java | 16 +- .../ui/chat/drawer/BufferItem.java | 144 +++++++ .../ui/chat/drawer/BufferWrapper.java | 78 ---- .../ui/chat/drawer/NetworkItem.java | 173 ++++++++ .../ui/chat/drawer/NetworkWrapper.java | 275 ------------- .../java/de/kuschku/util/AndroidAssert.java | 121 +++++- .../java/de/kuschku/util/DrawerUtils.java | 26 -- .../java/de/kuschku/util/ReflectionUtils.java | 9 +- .../keyboardutils/DialogKeyboardUtil.java | 32 ++ .../keyboardutils/EditTextKeyboardUtil.java | 62 +++ .../de/kuschku/util/niohelpers/Helper.java | 32 ++ .../callbacks/GeneralCallback.java | 5 + .../wrappers/GeneralCallbackWrapper.java | 28 ++ .../wrappers/GeneralUICallbackWrapper.java | 42 ++ .../ChildParentObservableSortedList.java | 83 ++++ .../lists/ObservableSortedList.java | 27 +- .../java/de/kuschku/util/ui/MessageUtil.java | 167 ++++++++ app/src/main/res/layout/activity_chat.xml | 26 ++ app/src/main/res/layout/activity_main.xml | 37 -- app/src/main/res/layout/core_dialog.xml | 19 - app/src/main/res/layout/dialog_address.xml | 33 ++ app/src/main/res/layout/dialog_login.xml | 32 ++ app/src/main/res/layout/drawer.xml | 47 --- app/src/main/res/layout/login_dialog.xml | 19 - app/src/main/res/layout/toolbar.xml | 3 +- .../main/res/layout/widget_chatmessage.xml | 4 +- app/src/main/res/values/styles.xml | 4 +- app/src/main/res/values/themes.xml | 17 + 80 files changed, 3256 insertions(+), 829 deletions(-) create mode 100644 app/src/main/java/de/kuschku/libquassel/events/LagChangedEvent.java create mode 100644 app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java create mode 100644 app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java create mode 100644 app/src/main/java/de/kuschku/libquassel/functions/types/Heartbeat.java create mode 100644 app/src/main/java/de/kuschku/libquassel/functions/types/HeartbeatReply.java create mode 100644 app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java create mode 100644 app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java rename app/src/main/java/de/kuschku/libquassel/{ => localtypes}/backlogmanagers/BacklogManager.java (57%) rename app/src/main/java/de/kuschku/libquassel/{ => localtypes}/backlogmanagers/SimpleBacklogManager.java (56%) create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/Synced.java create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/types/AliasManager.java create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppContext.java create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigWrapper.java create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java delete mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferWrapper.java create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java delete mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkWrapper.java delete mode 100644 app/src/main/java/de/kuschku/util/DrawerUtils.java create mode 100644 app/src/main/java/de/kuschku/util/keyboardutils/DialogKeyboardUtil.java create mode 100644 app/src/main/java/de/kuschku/util/keyboardutils/EditTextKeyboardUtil.java create mode 100644 app/src/main/java/de/kuschku/util/niohelpers/Helper.java create mode 100644 app/src/main/java/de/kuschku/util/observables/callbacks/GeneralCallback.java create mode 100644 app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralCallbackWrapper.java create mode 100644 app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralUICallbackWrapper.java create mode 100644 app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java create mode 100644 app/src/main/java/de/kuschku/util/ui/MessageUtil.java create mode 100644 app/src/main/res/layout/activity_chat.xml delete mode 100644 app/src/main/res/layout/activity_main.xml delete mode 100644 app/src/main/res/layout/core_dialog.xml create mode 100644 app/src/main/res/layout/dialog_address.xml create mode 100644 app/src/main/res/layout/dialog_login.xml delete mode 100644 app/src/main/res/layout/drawer.xml delete mode 100644 app/src/main/res/layout/login_dialog.xml diff --git a/app/build.gradle b/app/build.gradle index 398dbe519..44e183dac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,14 +46,10 @@ android { } - dependencies { testCompile 'junit:junit:4.12' - compile('com.mikepenz:materialdrawer:5.0.0.fastAdapter.b5-SNAPSHOT@aar') { - transitive = true - } - compile('com.mikepenz:fastadapter:0.4.2-SNAPSHOT@aar') { + compile('com.mikepenz:materialdrawer:5.0.0.b21-SNAPSHOT@aar') { transitive = true } compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fe1d777f2..2be27dbc7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,7 @@ android:name=".ui.chat.ChatActivity" android:label="@string/app_name" android:launchMode="singleTask" - android:theme="@style/AppTheme.Light"> + android:theme="@style/Quassel"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> diff --git a/app/src/main/java/de/kuschku/libquassel/Client.java b/app/src/main/java/de/kuschku/libquassel/Client.java index 1024106b1..47831854c 100644 --- a/app/src/main/java/de/kuschku/libquassel/Client.java +++ b/app/src/main/java/de/kuschku/libquassel/Client.java @@ -2,6 +2,7 @@ package de.kuschku.libquassel; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.util.Log; import java.util.ArrayList; import java.util.Collection; @@ -9,28 +10,33 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import de.kuschku.libquassel.backlogmanagers.BacklogManager; -import de.kuschku.libquassel.backlogmanagers.SimpleBacklogManager; +import de.kuschku.libquassel.events.LagChangedEvent; +import de.kuschku.libquassel.localtypes.NotificationManager; +import de.kuschku.libquassel.localtypes.backlogmanagers.BacklogManager; +import de.kuschku.libquassel.localtypes.backlogmanagers.SimpleBacklogManager; import de.kuschku.libquassel.events.ConnectionChangeEvent; import de.kuschku.libquassel.events.StatusMessageEvent; +import de.kuschku.libquassel.functions.types.HandshakeFunction; import de.kuschku.libquassel.functions.types.InitRequestFunction; import de.kuschku.libquassel.functions.types.RpcCallFunction; import de.kuschku.libquassel.localtypes.Buffer; import de.kuschku.libquassel.localtypes.Buffers; import de.kuschku.libquassel.message.Message; import de.kuschku.libquassel.objects.types.ClientInitAck; +import de.kuschku.libquassel.objects.types.ClientLogin; import de.kuschku.libquassel.objects.types.SessionState; import de.kuschku.libquassel.primitives.types.BufferInfo; import de.kuschku.libquassel.primitives.types.QVariant; import de.kuschku.libquassel.syncables.types.BufferSyncer; +import de.kuschku.libquassel.syncables.types.BufferViewConfig; import de.kuschku.libquassel.syncables.types.BufferViewManager; +import de.kuschku.libquassel.syncables.types.IgnoreListManager; +import de.kuschku.libquassel.syncables.types.IrcChannel; +import de.kuschku.libquassel.syncables.types.IrcUser; import de.kuschku.libquassel.syncables.types.Network; import de.kuschku.libquassel.syncables.types.SyncableObject; -import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkWrapper; import de.kuschku.util.backports.Stream; -import de.kuschku.util.observables.callbacks.UICallback; -import de.kuschku.util.observables.lists.IObservableList; -import de.kuschku.util.observables.lists.ObservableComparableSortedList; +import de.kuschku.util.observables.lists.ObservableElementList; import static de.kuschku.util.AndroidAssert.assertNotNull; @@ -39,7 +45,7 @@ public class Client { @NonNull private final Map<Integer, Network> networks = new HashMap<>(); @NonNull - private final IObservableList<UICallback, NetworkWrapper> networkList = new ObservableComparableSortedList<>(NetworkWrapper.class); + private final ObservableElementList<Integer> networkList = new ObservableElementList<>(); @NonNull private final Map<Integer, Buffer> buffers = new HashMap<>(); @NonNull @@ -47,8 +53,10 @@ public class Client { @NonNull private final BacklogManager backlogManager; @NonNull + private final NotificationManager notificationManager = new NotificationManager(); + @NonNull private final BusProvider busProvider; - public int lag; + private long lag; private ConnectionChangeEvent.Status connectionStatus; private ClientInitAck core; @Nullable @@ -56,6 +64,7 @@ public class Client { private BufferViewManager bufferViewManager; private BufferSyncer bufferSyncer; private ClientData clientData; + private IgnoreListManager ignoreListManager; public Client(@NonNull final BusProvider busProvider) { this(new SimpleBacklogManager(busProvider), busProvider); @@ -64,6 +73,7 @@ public class Client { public Client(@NonNull final BacklogManager backlogManager, @NonNull final BusProvider busProvider) { this.backlogManager = backlogManager; this.busProvider = busProvider; + this.backlogManager.setClient(this); } public void sendInput(@NonNull final BufferInfo info, @NonNull final String input) { @@ -86,6 +96,7 @@ public class Client { assertNotNull(state); networks.put(network.getNetworkId(), network); + networkList.add(network.getNetworkId()); for (BufferInfo info : state.BufferInfos) { if (info.networkId == network.getNetworkId()) { @@ -104,6 +115,7 @@ public class Client { public void putBuffer(@NonNull final Buffer buffer) { this.buffers.put(buffer.getInfo().id, buffer); + this.notificationManager.init(buffer.getInfo().id); } @Nullable @@ -111,11 +123,11 @@ public class Client { return this.buffers.get(bufferId); } - void sendInitRequest(@NonNull final String className, @Nullable final String objectName) { + public void sendInitRequest(@NonNull final String className, @Nullable final String objectName) { sendInitRequest(className, objectName, false); } - void sendInitRequest(@NonNull final String className, @Nullable final String objectName, boolean addToList) { + public void sendInitRequest(@NonNull final String className, @Nullable final String objectName, boolean addToList) { busProvider.dispatch(new InitRequestFunction(className, objectName)); if (addToList) @@ -135,33 +147,40 @@ public class Client { } @Nullable - public SyncableObject getObjectByIdentifier(@NonNull final String className, @NonNull final String objectName) { + public SyncableObject getObjectByIdentifier(@NonNull final String className, @Nullable final String objectName) { switch (className) { case "BacklogManager": return getBacklogManager(); case "IrcChannel": { + assertNotNull(objectName); final int networkId = Integer.parseInt(objectName.split("/")[0]); final String channelname = objectName.split("/")[1]; // Assert that networkId is valid Network network = getNetwork(networkId); assertNotNull(network); - return network.getChannels().get(channelname); + IrcChannel channel = network.getChannels().get(channelname); + assertNotNull("Channel " + channelname + " not found in " + network.getChannels().keySet(), channel); + return channel; } case "BufferSyncer": return bufferSyncer; case "BufferViewConfig": assertNotNull(getBufferViewManager()); - + assertNotNull(objectName); return getBufferViewManager().BufferViews.get(Integer.valueOf(objectName)); case "IrcUser": { + assertNotNull(objectName); final int networkId = Integer.parseInt(objectName.split("/")[0]); final String username = objectName.split("/")[1]; Network network = getNetwork(networkId); assertNotNull(network); - return network.getUser(username); + IrcUser networkUser = network.getUser(username); + assertNotNull("User " + username + " not found in " + network.getUsers().keySet(), networkUser); + return networkUser; } case "Network": { + assertNotNull(objectName); return getNetwork(Integer.parseInt(objectName)); } default: @@ -184,7 +203,7 @@ public class Client { } @NonNull - public BacklogManager getBacklogManager() { + public BacklogManager<?> getBacklogManager() { return backlogManager; } @@ -226,8 +245,8 @@ public class Client { } @NonNull - public Collection<Network> getNetworks() { - return networks.values(); + public ObservableElementList<Integer> getNetworks() { + return networkList; } @NonNull @@ -240,8 +259,31 @@ public class Client { busProvider.sendEvent(new ConnectionChangeEvent(connectionStatus)); } + public void login(String username, String password) { + busProvider.dispatch(new HandshakeFunction(new ClientLogin( + username, password + ))); + } + @NonNull - public IObservableList<UICallback, NetworkWrapper> getNetworkList() { - return networkList; + public NotificationManager getNotificationManager() { + return notificationManager; + } + + public void setLag(long l) { + lag = l; + busProvider.sendEvent(new LagChangedEvent(lag)); + } + + public long getLag() { + return lag; + } + + public IgnoreListManager getIgnoreListManager() { + return ignoreListManager; + } + + public void setIgnoreListManager(IgnoreListManager ignoreListManager) { + this.ignoreListManager = ignoreListManager; } } diff --git a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java index 003d968aa..23387e781 100644 --- a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java +++ b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java @@ -5,6 +5,8 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; +import org.joda.time.DateTime; + import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; @@ -20,6 +22,7 @@ import de.kuschku.libquassel.events.ConnectionChangeEvent; import de.kuschku.libquassel.events.GeneralErrorEvent; import de.kuschku.libquassel.events.HandshakeFailedEvent; import de.kuschku.libquassel.functions.types.HandshakeFunction; +import de.kuschku.libquassel.functions.types.Heartbeat; import de.kuschku.libquassel.objects.types.ClientInit; import de.kuschku.libquassel.primitives.QMetaTypeRegistry; import de.kuschku.libquassel.primitives.serializers.ProtocolSerializer; @@ -50,7 +53,9 @@ public class CoreConnection { @Nullable private ExecutorService outputExecutor; @Nullable - private ExecutorService inputExecutor; + private EndableThread inputThread; + @Nullable + private EndableThread heartbeatThread; @Nullable private RemotePeer remotePeer; @Nullable @@ -95,7 +100,6 @@ public class CoreConnection { // Create executor for write events outputExecutor = Executors.newSingleThreadExecutor(); - inputExecutor = Executors.newSingleThreadExecutor(); // Execute handshake handshake(); @@ -112,7 +116,8 @@ public class CoreConnection { client.setConnectionStatus(ConnectionChangeEvent.Status.DISCONNECTED); // We can do this because we clean up the file handles ourselves - if (inputExecutor != null) inputExecutor.shutdownNow(); + if (inputThread != null) inputThread.end(); + if (heartbeatThread != null) heartbeatThread.end(); if (outputExecutor != null) outputExecutor.shutdownNow(); // Which we do exactly here @@ -154,7 +159,6 @@ public class CoreConnection { */ private void handshake() throws IOException { assertNotNull(channel); - assertNotNull(inputExecutor); // Start protocol handshake with magic version and feature flags QMetaTypeRegistry.serialize(UInt, channel, 0x42b33f00 | clientData.flags.flags); @@ -166,7 +170,10 @@ public class CoreConnection { QMetaTypeRegistry.serialize(UInt, channel, 0x01 << 31); // Spawn and start a new read thread - inputExecutor.submit(new ReadRunnable()); + inputThread = new ReadThread(); + heartbeatThread = new HeartbeatThread(); + inputThread.start(); + heartbeatThread.start(); } public void onEventAsync(HandshakeFailedEvent event) { @@ -192,14 +199,20 @@ public class CoreConnection { /** * A runnable that reads from the channel and calls the functions responsible for processing the read data. */ - private class ReadRunnable implements Runnable { + private class ReadThread extends EndableThread { + private boolean running = true; + + public ReadThread() { + setName(getClass().getSimpleName()); + } + @Override public void run() { assertNotNull(client); try { boolean hasReadPreHandshake = false; - while (true) { + while (running) { if (!hasReadPreHandshake) { final ByteBuffer buffer = ByteBuffer.allocate(4); assertNotNull(buffer); @@ -248,5 +261,42 @@ public class CoreConnection { busProvider.sendEvent(new GeneralErrorEvent(e)); } } + + @Override + public void end() { + running = false; + } + } + + private class HeartbeatThread extends EndableThread { + private boolean running = true; + + public HeartbeatThread() { + setName(getClass().getSimpleName()); + } + + @Override + public void run() { + try { + assertNotNull(client); + + while (running) { + busProvider.dispatch(new Heartbeat(DateTime.now())); + + Thread.sleep(30 * 1000); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public void end() { + running = false; + } + } + + private abstract class EndableThread extends Thread { + public abstract void end(); } } diff --git a/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java b/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java index 53353c2e7..ef0fdb7e7 100644 --- a/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java +++ b/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java @@ -21,15 +21,15 @@ public interface IProtocolHandler { void onEventMainThread(SyncFunction packedFunc); - void onEventMainThread(ClientInitReject message); + void onEvent(ClientInitReject message); - void onEventMainThread(ClientInitAck message); + void onEvent(ClientInitAck message); - void onEventMainThread(ClientLoginAck message); + void onEvent(ClientLoginAck message); - void onEventMainThread(ClientLoginReject message); + void onEvent(ClientLoginReject message); - void onEventMainThread(SessionInit message); + void onEvent(SessionInit message); @NonNull Client getClient(); diff --git a/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java b/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java index ef9f947eb..6cafde2d2 100644 --- a/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java +++ b/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java @@ -3,12 +3,16 @@ package de.kuschku.libquassel; import android.support.annotation.NonNull; import android.util.Log; +import org.joda.time.DateTime; + import de.kuschku.libquassel.events.ConnectionChangeEvent; import de.kuschku.libquassel.events.GeneralErrorEvent; import de.kuschku.libquassel.events.HandshakeFailedEvent; import de.kuschku.libquassel.events.LoginFailedEvent; import de.kuschku.libquassel.events.LoginSuccessfulEvent; import de.kuschku.libquassel.exceptions.UnknownTypeException; +import de.kuschku.libquassel.functions.types.Heartbeat; +import de.kuschku.libquassel.functions.types.HeartbeatReply; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.functions.types.InitRequestFunction; import de.kuschku.libquassel.functions.types.RpcCallFunction; @@ -20,10 +24,13 @@ import de.kuschku.libquassel.objects.types.ClientLoginReject; import de.kuschku.libquassel.objects.types.SessionInit; import de.kuschku.libquassel.primitives.types.BufferInfo; import de.kuschku.libquassel.syncables.SyncableRegistry; +import de.kuschku.libquassel.syncables.types.BufferViewConfig; import de.kuschku.libquassel.syncables.types.SyncableObject; import de.kuschku.util.AndroidAssert; import de.kuschku.util.ReflectionUtils; +import static de.kuschku.util.AndroidAssert.assertNotNull; + public class ProtocolHandler implements IProtocolHandler { @NonNull public final Client client; @@ -51,7 +58,7 @@ public class ProtocolHandler implements IProtocolHandler { } } SyncableObject object = SyncableRegistry.from(packedFunc); - AndroidAssert.assertNotNull(object); + assertNotNull(object); object.init(packedFunc, busProvider, client); } catch (Exception e) { @@ -79,21 +86,21 @@ public class ProtocolHandler implements IProtocolHandler { public void onEventMainThread(@NonNull SyncFunction packedFunc) { try { final Object syncable = client.getObjectByIdentifier(packedFunc.className, packedFunc.objectName); - if (syncable != null) { - ReflectionUtils.invokeMethod(syncable, packedFunc.methodName, packedFunc.params); - } else { - busProvider.sendEvent(new GeneralErrorEvent(new UnknownTypeException(packedFunc.className))); - } + AndroidAssert.assertNotNull("Object not found: " + packedFunc.className+":"+packedFunc.objectName, syncable); + ReflectionUtils.invokeMethod(syncable, packedFunc.methodName, packedFunc.params); } catch (Exception e) { - busProvider.sendEvent(new GeneralErrorEvent(e)); + busProvider.sendEvent(new GeneralErrorEvent(e, packedFunc.toString())); + } catch (Error e) { + e.printStackTrace(); + Log.e("EVENT", packedFunc.toString()); } } - public void onEventMainThread(@NonNull ClientInitReject message) { + public void onEvent(@NonNull ClientInitReject message) { busProvider.sendEvent(new HandshakeFailedEvent(message.Error)); } - public void onEventMainThread(ClientInitAck message) { + public void onEvent(ClientInitAck message) { client.setCore(message); if (client.getCore().Configured) { @@ -105,16 +112,16 @@ public class ProtocolHandler implements IProtocolHandler { } } - public void onEventMainThread(ClientLoginAck message) { + public void onEvent(ClientLoginAck message) { busProvider.sendEvent(new LoginSuccessfulEvent()); client.setConnectionStatus(ConnectionChangeEvent.Status.CONNECTING); } - public void onEventMainThread(@NonNull ClientLoginReject message) { + public void onEvent(@NonNull ClientLoginReject message) { busProvider.sendEvent(new LoginFailedEvent(message.Error)); } - public void onEventMainThread(@NonNull SessionInit message) { + public void onEvent(@NonNull SessionInit message) { client.setState(message.SessionState); client.setConnectionStatus(ConnectionChangeEvent.Status.INITIALIZING_DATA); @@ -134,6 +141,17 @@ public class ProtocolHandler implements IProtocolHandler { } } + public void onEvent(@NonNull Heartbeat heartbeat) { + busProvider.dispatch(new HeartbeatReply(heartbeat.dateTime)); + } + + public void onEventMainThread(@NonNull HeartbeatReply heartbeat) { + long roundtrip = DateTime.now().getMillis() - heartbeat.dateTime.getMillis(); + long lag = (long) (roundtrip * 0.5); + + client.setLag(lag); + } + @NonNull @Override public Client getClient() { diff --git a/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java b/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java index 1a9c1f93a..e16606c29 100644 --- a/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java +++ b/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java @@ -4,7 +4,7 @@ import android.support.annotation.IntRange; @SuppressWarnings("WeakerAccess") public class BacklogReceivedEvent { - @IntRange(from = 0) + @IntRange(from = -1) public final int bufferId; public BacklogReceivedEvent(int bufferId) { diff --git a/app/src/main/java/de/kuschku/libquassel/events/GeneralErrorEvent.java b/app/src/main/java/de/kuschku/libquassel/events/GeneralErrorEvent.java index 4f3b5db6c..9a06e6d02 100644 --- a/app/src/main/java/de/kuschku/libquassel/events/GeneralErrorEvent.java +++ b/app/src/main/java/de/kuschku/libquassel/events/GeneralErrorEvent.java @@ -13,11 +13,18 @@ public class GeneralErrorEvent { this.exception = exception; } + public GeneralErrorEvent(Exception exception, String debugInfo) { + this.debugInfo = debugInfo; + this.exception = exception; + } + @Override public String toString() { if (debugInfo == null) return String.format("%s: %s", exception.getClass().getSimpleName(), exception.getLocalizedMessage()); - else + else if (exception == null) return debugInfo; + else + return String.format("%s: %s\n%s", exception.getClass().getSimpleName(), exception.getLocalizedMessage(), debugInfo); } } diff --git a/app/src/main/java/de/kuschku/libquassel/events/LagChangedEvent.java b/app/src/main/java/de/kuschku/libquassel/events/LagChangedEvent.java new file mode 100644 index 000000000..4b5cdcf56 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/events/LagChangedEvent.java @@ -0,0 +1,16 @@ +package de.kuschku.libquassel.events; + +public class LagChangedEvent { + public final long lag; + + public LagChangedEvent(long lag) { + this.lag = lag; + } + + @Override + public String toString() { + return "LagChangedEvent{" + + "lag=" + lag + + '}'; + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/exceptions/UnknownTypeException.java b/app/src/main/java/de/kuschku/libquassel/exceptions/UnknownTypeException.java index 97b2d2a81..f7ac88da4 100644 --- a/app/src/main/java/de/kuschku/libquassel/exceptions/UnknownTypeException.java +++ b/app/src/main/java/de/kuschku/libquassel/exceptions/UnknownTypeException.java @@ -4,9 +4,15 @@ import android.support.annotation.Nullable; public class UnknownTypeException extends IllegalArgumentException { public final String typeName; + public final String additionalData; public UnknownTypeException(String typeName) { + this(typeName, null); + } + + public UnknownTypeException(String typeName, Object additionalData) { this.typeName = typeName; + this.additionalData = String.valueOf(additionalData); } @Nullable @@ -17,6 +23,6 @@ public class UnknownTypeException extends IllegalArgumentException { @Override public String getMessage() { - return String.format("Unknown type: %s", typeName); + return String.format("Unknown type: %s; %s", typeName, additionalData); } } diff --git a/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java new file mode 100644 index 000000000..ffc10823f --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java @@ -0,0 +1,46 @@ +package de.kuschku.libquassel.functions.serializers; + +import android.support.annotation.NonNull; + +import org.joda.time.DateTime; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import de.kuschku.libquassel.functions.FunctionType; +import de.kuschku.libquassel.functions.types.Heartbeat; +import de.kuschku.libquassel.functions.types.HeartbeatReply; +import de.kuschku.libquassel.primitives.types.QVariant; + +import static de.kuschku.util.AndroidAssert.assertTrue; + +public class HeartbeatReplySerializer implements FunctionSerializer<HeartbeatReply> { + @NonNull + private static final HeartbeatReplySerializer serializer = new HeartbeatReplySerializer(); + + private HeartbeatReplySerializer() { + } + + @NonNull + public static HeartbeatReplySerializer get() { + return serializer; + } + + @NonNull + @Override + public List serialize(@NonNull HeartbeatReply data) { + return Arrays.asList( + FunctionType.HEARTBEATREPLY.id, + new QVariant<>(data.dateTime) + ); + } + + @NonNull + @Override + public HeartbeatReply deserialize(@NonNull List packedFunc) { + assertTrue(packedFunc.size() == 1); + + return new HeartbeatReply((DateTime) ((QVariant) packedFunc.remove(0)).data); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java new file mode 100644 index 000000000..ec2e4c59d --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java @@ -0,0 +1,44 @@ +package de.kuschku.libquassel.functions.serializers; + +import android.support.annotation.NonNull; + +import org.joda.time.DateTime; + +import java.util.Arrays; +import java.util.List; + +import de.kuschku.libquassel.functions.FunctionType; +import de.kuschku.libquassel.functions.types.Heartbeat; +import de.kuschku.libquassel.primitives.types.QVariant; + +import static de.kuschku.util.AndroidAssert.assertTrue; + +public class HeartbeatSerializer implements FunctionSerializer<Heartbeat> { + @NonNull + private static final HeartbeatSerializer serializer = new HeartbeatSerializer(); + + private HeartbeatSerializer() { + } + + @NonNull + public static HeartbeatSerializer get() { + return serializer; + } + + @NonNull + @Override + public List serialize(@NonNull Heartbeat data) { + return Arrays.asList( + FunctionType.HEARTBEAT.id, + new QVariant<>(data.dateTime) + ); + } + + @NonNull + @Override + public Heartbeat deserialize(@NonNull List packedFunc) { + assertTrue(packedFunc.size() == 1); + + return new Heartbeat((DateTime) ((QVariant) packedFunc.remove(0)).data); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/functions/types/Heartbeat.java b/app/src/main/java/de/kuschku/libquassel/functions/types/Heartbeat.java new file mode 100644 index 000000000..cf10f69f7 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/functions/types/Heartbeat.java @@ -0,0 +1,18 @@ +package de.kuschku.libquassel.functions.types; + +import org.joda.time.DateTime; + +public class Heartbeat { + public final DateTime dateTime; + + public Heartbeat(DateTime dateTime) { + this.dateTime = dateTime; + } + + @Override + public String toString() { + return "Heartbeat{" + + "dateTime=" + dateTime + + '}'; + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/functions/types/HeartbeatReply.java b/app/src/main/java/de/kuschku/libquassel/functions/types/HeartbeatReply.java new file mode 100644 index 000000000..ecfe819d5 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/functions/types/HeartbeatReply.java @@ -0,0 +1,18 @@ +package de.kuschku.libquassel.functions.types; + +import org.joda.time.DateTime; + +public class HeartbeatReply { + public final DateTime dateTime; + + public HeartbeatReply(DateTime dateTime) { + this.dateTime = dateTime; + } + + @Override + public String toString() { + return "HeartbeatReply{" + + "dateTime=" + dateTime + + '}'; + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java b/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java index 3a29426f9..40b7c73c8 100644 --- a/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java @@ -11,4 +11,6 @@ public interface Buffer { @Nullable String getName(); + + boolean isActive(); } diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java b/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java index 4e0f977bd..2865b1a63 100644 --- a/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java @@ -9,10 +9,10 @@ import de.kuschku.libquassel.syncables.types.IrcChannel; public class ChannelBuffer implements Buffer { @NonNull private final BufferInfo info; - @NonNull + @Nullable private final IrcChannel channel; - public ChannelBuffer(@NonNull BufferInfo info, @NonNull IrcChannel channel) { + public ChannelBuffer(@NonNull BufferInfo info, @Nullable IrcChannel channel) { this.info = info; this.channel = channel; } @@ -29,7 +29,12 @@ public class ChannelBuffer implements Buffer { return getInfo().name; } - @NonNull + @Override + public boolean isActive() { + return channel != null; + } + + @Nullable public IrcChannel getChannel() { return channel; } diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java new file mode 100644 index 000000000..5dd652996 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java @@ -0,0 +1,18 @@ +package de.kuschku.libquassel.localtypes; + +import android.util.SparseArray; + +import de.kuschku.libquassel.message.Message; +import de.kuschku.util.observables.lists.ObservableComparableSortedList; + +public class NotificationManager { + private SparseArray<ObservableComparableSortedList<Message>> notifications = new SparseArray<>(); + + public ObservableComparableSortedList<Message> getNotifications(int bufferid) { + return notifications.get(bufferid); + } + + public void init(int id) { + notifications.put(id, new ObservableComparableSortedList<>(Message.class)); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java b/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java index 9aea13918..b64c6be50 100644 --- a/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java @@ -29,6 +29,11 @@ public class QueryBuffer implements Buffer { return getInfo().name; } + @Override + public boolean isActive() { + return user != null; + } + @Nullable public IrcUser getUser() { return user; diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java b/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java index 8218fbbc5..bc25d633c 100644 --- a/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java @@ -29,6 +29,11 @@ public class StatusBuffer implements Buffer { return network.getNetworkName(); } + @Override + public boolean isActive() { + return network.isConnected(); + } + @NonNull @Override public String toString() { diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java new file mode 100644 index 000000000..ec15e0623 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java @@ -0,0 +1,72 @@ +package de.kuschku.libquassel.localtypes.backlogmanagers; + +import android.support.annotation.NonNull; + +import com.android.internal.util.Predicate; + +import de.kuschku.libquassel.Client; +import de.kuschku.libquassel.message.Message; +import de.kuschku.quasseldroid_ng.ui.AppContext; +import de.kuschku.util.observables.callbacks.UICallback; +import de.kuschku.util.observables.lists.ObservableSortedList; + +public class BacklogFilter implements UICallback { + @NonNull + private final Client client; + @NonNull + private final ObservableSortedList<Message> unfiltered; + @NonNull + private final ObservableSortedList<Message> filtered; + + public BacklogFilter(@NonNull Client client, @NonNull ObservableSortedList<Message> unfiltered, @NonNull ObservableSortedList<Message> filtered) { + this.client = client; + this.unfiltered = unfiltered; + this.filtered = filtered; + } + + @Override + public void notifyItemInserted(int position) { + Message message = unfiltered.get(position); + if (filterItem(message)) filtered.add(message); + } + + private boolean filterItem(Message message) { + return !client.getIgnoreListManager().matches(message); + } + + @Override + public void notifyItemChanged(int position) { + filtered.notifyItemChanged(position); + } + + @Override + public void notifyItemRemoved(int position) { + filtered.remove(position); + } + + @Override + public void notifyItemMoved(int from, int to) { + // Can’t occur: Sorted List + } + + @Override + public void notifyItemRangeInserted(int position, int count) { + for (int i = position; i < position + count; i++) { + notifyItemInserted(i); + } + } + + @Override + public void notifyItemRangeChanged(int position, int count) { + for (int i = position; i < position + count; i++) { + notifyItemChanged(i); + } + } + + @Override + public void notifyItemRangeRemoved(int position, int count) { + for (int i = position; i < position + count; i++) { + notifyItemRemoved(i); + } + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java similarity index 57% rename from app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java rename to app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java index 3c0b700c7..df71178de 100644 --- a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java @@ -1,26 +1,32 @@ -package de.kuschku.libquassel.backlogmanagers; +package de.kuschku.libquassel.localtypes.backlogmanagers; +import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import java.util.List; +import de.kuschku.libquassel.Client; import de.kuschku.libquassel.message.Message; import de.kuschku.libquassel.syncables.types.SyncableObject; import de.kuschku.util.observables.AutoScroller; import de.kuschku.util.observables.lists.ObservableSortedList; -public abstract class BacklogManager extends SyncableObject { +public abstract class BacklogManager<T extends BacklogManager<T>> extends SyncableObject<T> { public abstract void requestBacklog(int bufferId, int from, int to, int count, int extra); public abstract void receiveBacklog(int bufferId, int from, int to, int count, int extra, @NonNull List<Message> messages); public abstract void displayMessage(int bufferId, @NonNull Message message); - public abstract ObservableSortedList<Message> get(int bufferId); + public abstract ObservableSortedList<Message> get(@IntRange(from = -1) int bufferId); - public abstract void bind(int bufferId, @NonNull RecyclerView.Adapter adapter, @Nullable AutoScroller scroller); + public abstract ObservableSortedList<Message> getFiltered(@IntRange(from = -1) int bufferId); + + public abstract BacklogFilter getFilter(@IntRange(from = -1) int bufferId); public abstract void requestMoreBacklog(int bufferId, int count); + + public abstract void setClient(Client client); } diff --git a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java similarity index 56% rename from app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java rename to app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java index 118408905..edfee61b3 100644 --- a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java @@ -1,14 +1,16 @@ -package de.kuschku.libquassel.backlogmanagers; +package de.kuschku.libquassel.localtypes.backlogmanagers; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.util.SparseArray; import com.google.common.collect.Lists; import java.util.List; +import java.util.Map; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; @@ -24,18 +26,28 @@ import de.kuschku.util.observables.lists.ObservableSortedList; import static de.kuschku.util.AndroidAssert.assertNotNull; -public class SimpleBacklogManager extends BacklogManager { +public class SimpleBacklogManager extends BacklogManager<SimpleBacklogManager> { @NonNull private final SparseArray<ObservableSortedList<Message>> backlogs = new SparseArray<>(); @NonNull + private final SparseArray<ObservableSortedList<Message>> filteredBacklogs = new SparseArray<>(); + @NonNull + private final SparseArray<BacklogFilter> filters = new SparseArray<>(); + @NonNull private final BusProvider busProvider; + @Nullable + private Client client; public SimpleBacklogManager(@NonNull BusProvider busProvider) { this.busProvider = busProvider; } + public void setClient(@Nullable Client client) { + this.client = client; + } + public void requestBacklog(int bufferId, int from, int to, int count, int extra) { - busProvider.dispatch(new SyncFunction("BacklogManager", "", "requestBacklog", Lists.newArrayList( + busProvider.dispatch(new SyncFunction<>("BacklogManager", "", "requestBacklog", Lists.newArrayList( new QVariant<>("BufferId", bufferId), new QVariant<>("MsgId", from), new QVariant<>("MsgId", to), @@ -45,7 +57,7 @@ public class SimpleBacklogManager extends BacklogManager { } public void receiveBacklog(@IntRange(from = 0) int bufferId, int from, int to, int count, int extra, @NonNull List<Message> messages) { - get(bufferId).list.addAll(messages); + get(bufferId).addAll(messages); busProvider.sendEvent(new BacklogReceivedEvent(bufferId)); } @@ -55,7 +67,7 @@ public class SimpleBacklogManager extends BacklogManager { ObservableSortedList<Message> messages = get(bufferId); assertNotNull(messages); - messages.list.add(message); + messages.add(message); } public void bind(@IntRange(from = 0) int bufferId, @NonNull RecyclerView.Adapter adapter, @Nullable AutoScroller scroller) { @@ -70,24 +82,54 @@ public class SimpleBacklogManager extends BacklogManager { ObservableSortedList<Message> backlog = backlogs.get(bufferId); int messageId = (backlog == null) ? -1 : - (backlog.last() == null) ? -1 : - backlog.last().messageId; + (backlog.last() == null) ? -1 : + backlog.last().messageId; requestBacklog(bufferId, -1, messageId, count, 0); } - public ObservableSortedList<Message> get(@IntRange(from = 0) int bufferId) { - if (backlogs.get(bufferId) == null) - backlogs.put(bufferId, new ObservableComparableSortedList<>(Message.class, true)); + public ObservableSortedList<Message> get(@IntRange(from = -1) int bufferId) { + ensureExisting(bufferId); - ObservableSortedList<Message> messages = backlogs.get(bufferId); - assertNotNull(messages); + return backlogs.get(bufferId); + } + + public ObservableSortedList<Message> getFiltered(@IntRange(from = -1) int bufferId) { + ensureExisting(bufferId); + + return filteredBacklogs.get(bufferId); + } + + public BacklogFilter getFilter(@IntRange(from = -1) int bufferId) { + ensureExisting(bufferId); - return messages; + return filters.get(bufferId); + } + + private void ensureExisting(@IntRange(from = -1) int bufferId) { + if (backlogs.get(bufferId) == null) { + ObservableComparableSortedList<Message> messages = new ObservableComparableSortedList<>(Message.class, true); + ObservableComparableSortedList<Message> filteredMessages = new ObservableComparableSortedList<>(Message.class, true); + BacklogFilter backlogFilter = new BacklogFilter(client, messages, filteredMessages); + messages.addCallback(backlogFilter); + backlogs.put(bufferId, messages); + filteredBacklogs.put(bufferId, filteredMessages); + filters.put(bufferId, backlogFilter); + } } @Override public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) { } + + @Override + public void update(SimpleBacklogManager from) { + + } + + @Override + public void update(Map<String, QVariant> from) { + + } } diff --git a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/CharSerializer.java b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/CharSerializer.java index 1f55e8cff..d993ba5e4 100644 --- a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/CharSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/CharSerializer.java @@ -21,16 +21,18 @@ public class CharSerializer implements PrimitiveSerializer<Character> { @Override public void serialize(@NonNull final ByteChannel channel, @NonNull final Character data) throws IOException { - final ByteBuffer buffer = Charset.forName("UTF-16BE").encode(String.valueOf(data.charValue())); - channel.write(buffer); + final ByteBuffer contentBuffer = Charset.forName("UTF-16BE").encode(String.copyValueOf(new char[]{data})); + channel.write(contentBuffer); } @NonNull @Override public Character deserialize(@NonNull final ByteBuffer buffer) throws IOException { - final ByteBuffer contentBuffer = ByteBuffer.allocate(2); - contentBuffer.put(buffer.array(), buffer.position(), 2); - buffer.position(buffer.position() + 2); - return Charset.forName("UTF-16BE").decode(buffer).get(); + int len = 2; + final ByteBuffer contentBuffer = ByteBuffer.allocate(len); + contentBuffer.put(buffer.array(), buffer.position(), len); + contentBuffer.position(0); + buffer.position(buffer.position() + len); + return Charset.forName("UTF-16BE").decode(contentBuffer).toString().charAt(0); } } diff --git a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java index 2b7690fdc..abe83b399 100644 --- a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java @@ -3,6 +3,7 @@ package de.kuschku.libquassel.primitives.serializers; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import de.kuschku.libquassel.primitives.types.BufferInfo; import org.joda.time.DateTime; import java.io.IOException; @@ -39,18 +40,22 @@ public class MessageSerializer implements PrimitiveSerializer<Message> { @Nullable @Override public Message deserialize(@NonNull final ByteBuffer buffer) throws IOException { + Integer messageId = IntSerializer.get().deserialize(buffer); + DateTime time = new DateTime(((long) IntSerializer.get().deserialize(buffer)) * 1000); + Message.Type type = Message.Type.fromId(IntSerializer.get().deserialize(buffer)); + Message.Flags flags = new Message.Flags(ByteSerializer.get().deserialize(buffer)); + BufferInfo bufferInfo = BufferInfoSerializer.get().deserialize(buffer); String sender = ByteArraySerializer.get().deserialize(buffer); String message = ByteArraySerializer.get().deserialize(buffer); assertNotNull(sender); assertNotNull(message); - return new Message( - IntSerializer.get().deserialize(buffer), - new DateTime(((long) IntSerializer.get().deserialize(buffer)) * 1000), - Message.Type.fromId(IntSerializer.get().deserialize(buffer)), - new Message.Flags(ByteSerializer.get().deserialize(buffer)), - BufferInfoSerializer.get().deserialize(buffer), + messageId, + time, + type, + flags, + bufferInfo, sender, message ); diff --git a/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java b/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java index 4938a2693..ed1322d3f 100644 --- a/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java +++ b/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java @@ -2,6 +2,7 @@ package de.kuschku.libquassel.protocols; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.util.Log; import com.google.common.base.Function; import com.google.common.collect.Lists; @@ -9,17 +10,25 @@ import com.google.common.collect.Lists; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.CoreConnection; import de.kuschku.libquassel.events.ConnectionChangeEvent; import de.kuschku.libquassel.events.GeneralErrorEvent; import de.kuschku.libquassel.functions.FunctionType; +import de.kuschku.libquassel.functions.serializers.HeartbeatReplySerializer; +import de.kuschku.libquassel.functions.serializers.HeartbeatSerializer; import de.kuschku.libquassel.functions.serializers.InitDataFunctionSerializer; import de.kuschku.libquassel.functions.serializers.InitRequestFunctionSerializer; import de.kuschku.libquassel.functions.serializers.PackedInitDataFunctionSerializer; @@ -28,6 +37,8 @@ import de.kuschku.libquassel.functions.serializers.PackedSyncFunctionSerializer; import de.kuschku.libquassel.functions.serializers.UnpackedRpcCallFunctionSerializer; import de.kuschku.libquassel.functions.serializers.UnpackedSyncFunctionSerializer; import de.kuschku.libquassel.functions.types.HandshakeFunction; +import de.kuschku.libquassel.functions.types.Heartbeat; +import de.kuschku.libquassel.functions.types.HeartbeatReply; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.functions.types.InitRequestFunction; import de.kuschku.libquassel.functions.types.RpcCallFunction; @@ -38,6 +49,7 @@ import de.kuschku.libquassel.primitives.serializers.IntSerializer; import de.kuschku.libquassel.primitives.serializers.PrimitiveSerializer; import de.kuschku.libquassel.primitives.serializers.VariantVariantListSerializer; import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.util.niohelpers.Helper; import de.kuschku.util.niohelpers.WrappedChannel; import static de.kuschku.util.AndroidAssert.assertNotNull; @@ -48,19 +60,22 @@ import static de.kuschku.util.AndroidAssert.assertNotNull; * * @author Janne Koschinski */ -@SuppressWarnings({"unchecked"}) +@SuppressWarnings({"unchecked", "unused"}) public class DatastreamPeer implements RemotePeer { @NonNull private final CoreConnection connection; @NonNull private final BusProvider busProvider; @NonNull + private final ExecutorService parseExecutor; + @NonNull private ByteBuffer buffer = ByteBuffer.allocate(0); public DatastreamPeer(@NonNull CoreConnection connection, @NonNull BusProvider busProvider) { this.connection = connection; this.busProvider = busProvider; this.busProvider.dispatch.register(this); + this.parseExecutor = Executors.newCachedThreadPool(); } @NonNull @@ -104,6 +119,7 @@ public class DatastreamPeer implements RemotePeer { } public void onEventBackgroundThread(@NonNull SyncFunction func) { + assertNotNull(connection.getOutputExecutor()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<SyncFunction>get(), UnpackedSyncFunctionSerializer.get().serialize(func) @@ -111,6 +127,7 @@ public class DatastreamPeer implements RemotePeer { } public void onEventBackgroundThread(@NonNull RpcCallFunction func) { + assertNotNull(connection.getOutputExecutor()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<RpcCallFunction>get(), UnpackedRpcCallFunctionSerializer.get().serialize(func) @@ -118,6 +135,7 @@ public class DatastreamPeer implements RemotePeer { } public void onEventBackgroundThread(@NonNull InitRequestFunction func) { + assertNotNull(connection.getOutputExecutor()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<InitRequestFunction>get(), InitRequestFunctionSerializer.get().serializePacked(func) @@ -125,16 +143,36 @@ public class DatastreamPeer implements RemotePeer { } public void onEventBackgroundThread(@NonNull InitDataFunction func) { + assertNotNull(connection.getOutputExecutor()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<InitDataFunction>get(), InitDataFunctionSerializer.get().serialize(func) )); } + public void onEventBackgroundThread(@NonNull Heartbeat func) { + assertNotNull(connection.getOutputExecutor()); + connection.getOutputExecutor().submit(new OutputRunnable<>( + VariantVariantListSerializer.<InitDataFunction>get(), + HeartbeatSerializer.get().serialize(func) + )); + } + + public void onEventBackgroundThread(@NonNull HeartbeatReply func) { + assertNotNull(connection.getOutputExecutor()); + connection.getOutputExecutor().submit(new OutputRunnable<>( + VariantVariantListSerializer.<InitDataFunction>get(), + HeartbeatReplySerializer.get().serialize(func) + )); + } + public void onEventBackgroundThread(@NonNull HandshakeFunction func) { + assertNotNull(connection.getOutputExecutor()); + Map<String, QVariant> variantMap = MessageTypeRegistry.toVariantMap(func.data).data; + assertNotNull(variantMap); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.get(), - DatastreamPeer.mapToList(MessageTypeRegistry.toVariantMap(func.data).data) + DatastreamPeer.mapToList(variantMap) )); } @@ -143,7 +181,7 @@ public class DatastreamPeer implements RemotePeer { } private void handlePackedFunc(@NonNull List<QVariant> data) { - final FunctionType type = FunctionType.fromId((Integer) data.remove(0).data); + final FunctionType type = FunctionType.fromId((int) data.remove(0).data); switch (type) { case SYNC: busProvider.handle(PackedSyncFunctionSerializer.get().deserialize(data)); @@ -158,7 +196,11 @@ public class DatastreamPeer implements RemotePeer { busProvider.handle(PackedInitDataFunctionSerializer.get().deserialize(data)); break; case HEARTBEAT: + busProvider.handle(HeartbeatSerializer.get().deserialize(data)); + break; case HEARTBEATREPLY: + busProvider.handle(HeartbeatReplySerializer.get().deserialize(data)); + break; default: busProvider.sendEvent(new GeneralErrorEvent("Unknown package received: " + data)); break; @@ -176,17 +218,7 @@ public class DatastreamPeer implements RemotePeer { buffer = ByteBuffer.allocate(size); connection.getChannel().read(buffer); - // TODO: Put this into a future with a time limit, and parallelize it. - final List data = VariantVariantListSerializer.get().deserialize(buffer); - if (connection.getStatus() == ConnectionChangeEvent.Status.CONNECTING - || connection.getStatus() == ConnectionChangeEvent.Status.HANDSHAKE - || connection.getStatus() == ConnectionChangeEvent.Status.CORE_SETUP_REQUIRED - || connection.getStatus() == ConnectionChangeEvent.Status.USER_SETUP_REQUIRED - || connection.getStatus() == ConnectionChangeEvent.Status.LOGIN_REQUIRED) { - handleHandshakeMessage(data); - } else { - handlePackedFunc(data); - } + parseExecutor.submit(new ParseRunnable(buffer)); } @NonNull @@ -238,4 +270,33 @@ public class DatastreamPeer implements RemotePeer { } } } + + private class ParseRunnable implements Runnable { + ByteBuffer buffer; + public ParseRunnable(ByteBuffer buffer) { + this.buffer = buffer; + } + + @Override + public void run() { + try { + // TODO: Put this into a future with a time limit, and parallelize it. + final List data = VariantVariantListSerializer.get().deserialize(buffer); + if (connection.getStatus() == ConnectionChangeEvent.Status.CONNECTING + || connection.getStatus() == ConnectionChangeEvent.Status.HANDSHAKE + || connection.getStatus() == ConnectionChangeEvent.Status.CORE_SETUP_REQUIRED + || connection.getStatus() == ConnectionChangeEvent.Status.USER_SETUP_REQUIRED + || connection.getStatus() == ConnectionChangeEvent.Status.LOGIN_REQUIRED) { + handleHandshakeMessage(data); + } else { + handlePackedFunc(data); + } + } catch (BufferUnderflowException|BufferOverflowException e) { + Helper.printHexDump(buffer.array()); + busProvider.sendEvent(new GeneralErrorEvent(e)); + } catch (Exception e) { + busProvider.sendEvent(new GeneralErrorEvent(e)); + } + } + } } diff --git a/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java b/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java index 53582f5ad..603fdd0c1 100644 --- a/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java +++ b/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java @@ -8,17 +8,24 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.CoreConnection; +import de.kuschku.libquassel.events.ConnectionChangeEvent; import de.kuschku.libquassel.events.GeneralErrorEvent; import de.kuschku.libquassel.functions.FunctionType; +import de.kuschku.libquassel.functions.serializers.HeartbeatReplySerializer; +import de.kuschku.libquassel.functions.serializers.HeartbeatSerializer; import de.kuschku.libquassel.functions.serializers.InitDataFunctionSerializer; import de.kuschku.libquassel.functions.serializers.InitRequestFunctionSerializer; import de.kuschku.libquassel.functions.serializers.UnpackedInitDataFunctionSerializer; import de.kuschku.libquassel.functions.serializers.UnpackedRpcCallFunctionSerializer; import de.kuschku.libquassel.functions.serializers.UnpackedSyncFunctionSerializer; import de.kuschku.libquassel.functions.types.HandshakeFunction; +import de.kuschku.libquassel.functions.types.Heartbeat; +import de.kuschku.libquassel.functions.types.HeartbeatReply; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.functions.types.InitRequestFunction; import de.kuschku.libquassel.functions.types.RpcCallFunction; @@ -30,10 +37,12 @@ import de.kuschku.libquassel.primitives.serializers.PrimitiveSerializer; import de.kuschku.libquassel.primitives.serializers.VariantSerializer; import de.kuschku.libquassel.primitives.serializers.VariantVariantListSerializer; import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.util.AndroidAssert; import de.kuschku.util.niohelpers.WrappedChannel; import static de.kuschku.libquassel.primitives.QMetaType.Type.QVariantList; import static de.kuschku.libquassel.primitives.QMetaType.Type.QVariantMap; +import static de.kuschku.util.AndroidAssert.*; /** * A helper class processing incoming and outgoing messages. @@ -41,22 +50,26 @@ import static de.kuschku.libquassel.primitives.QMetaType.Type.QVariantMap; * * @author Janne Koschinski */ -@SuppressWarnings({"unchecked"}) +@SuppressWarnings({"unchecked", "unused"}) public class LegacyPeer implements RemotePeer { @NonNull private final CoreConnection connection; @NonNull private final BusProvider busProvider; @NonNull + private final ExecutorService parseExecutor; + @NonNull private ByteBuffer buffer = ByteBuffer.allocate(0); public LegacyPeer(@NonNull CoreConnection connection, @NonNull BusProvider busProvider) { this.connection = connection; this.busProvider = busProvider; this.busProvider.dispatch.register(this); + this.parseExecutor = Executors.newCachedThreadPool(); } public final void onEventBackgroundThread(@NonNull SyncFunction func) { + assertNotNull(connection.getOutputExecutor()); final List serialize = UnpackedSyncFunctionSerializer.get().serialize(func); connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(), new QVariant(new QMetaType(List.class, QMetaType.Type.QVariantList, VariantVariantListSerializer.get()), @@ -64,21 +77,37 @@ public class LegacyPeer implements RemotePeer { } public void onEventBackgroundThread(@NonNull RpcCallFunction func) { + assertNotNull(connection.getOutputExecutor()); connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(), new QVariant<>(UnpackedRpcCallFunctionSerializer.get().serialize(func)))); } public void onEventBackgroundThread(@NonNull InitRequestFunction func) { + assertNotNull(connection.getOutputExecutor()); connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(), new QVariant<>(InitRequestFunctionSerializer.get().serialize(func)))); } public void onEventBackgroundThread(@NonNull InitDataFunction func) { + assertNotNull(connection.getOutputExecutor()); connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(), new QVariant<>(InitDataFunctionSerializer.get().serialize(func)))); } + public void onEventBackgroundThread(@NonNull Heartbeat func) { + assertNotNull(connection.getOutputExecutor()); + connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(), + new QVariant<>(HeartbeatSerializer.get().serialize(func)))); + } + + public void onEventBackgroundThread(@NonNull HeartbeatReply func) { + assertNotNull(connection.getOutputExecutor()); + connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(), + new QVariant<>(HeartbeatReplySerializer.get().serialize(func)))); + } + public void onEventBackgroundThread(@NonNull HandshakeFunction func) { + assertNotNull(connection.getOutputExecutor()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantSerializer.get(), MessageTypeRegistry.toVariantMap(func.data))); } @@ -93,32 +122,7 @@ public class LegacyPeer implements RemotePeer { buffer = ByteBuffer.allocate(size); connection.getChannel().read(buffer); - // TODO: Put this into a future with a time limit, and parallelize it. - final QVariant data = VariantSerializer.get().deserialize(buffer); - if (data.type.type == QVariantMap) { - busProvider.handle(MessageTypeRegistry.from((Map<String, QVariant>) data.data)); - } else if (data.type.type == QVariantList) { - final FunctionType type = FunctionType.fromId((Integer) ((List<Object>) data.data).remove(0)); - switch (type) { - case SYNC: - busProvider.handle(UnpackedSyncFunctionSerializer.get().deserialize((List<QVariant>) data.data)); - break; - case RPCCALL: - busProvider.handle(UnpackedRpcCallFunctionSerializer.get().deserialize((List<QVariant>) data.data)); - break; - case INITREQUEST: - busProvider.handle(InitRequestFunctionSerializer.get().deserialize((List<QVariant>) data.data)); - break; - case INITDATA: - busProvider.handle(UnpackedInitDataFunctionSerializer.get().deserialize((List<QVariant>) data.data)); - break; - case HEARTBEAT: - case HEARTBEATREPLY: - default: - busProvider.sendEvent(new GeneralErrorEvent("Unknown package received: " + data)); - break; - } - } + parseExecutor.submit(new ParseRunnable(buffer)); } @NonNull @@ -168,4 +172,50 @@ public class LegacyPeer implements RemotePeer { } } } + + private class ParseRunnable implements Runnable { + ByteBuffer buffer; + public ParseRunnable(ByteBuffer buffer) { + this.buffer = buffer; + } + + @Override + public void run() { + try { + // TODO: Put this into a future with a time limit, and parallelize it. + final QVariant data = VariantSerializer.get().deserialize(buffer); + assertNotNull(data.data); + if (data.type.type == QVariantMap) { + busProvider.handle(MessageTypeRegistry.from((Map<String, QVariant>) data.data)); + } else if (data.type.type == QVariantList) { + final FunctionType type = FunctionType.fromId((Integer) ((List<Object>) data.data).remove(0)); + switch (type) { + case SYNC: + busProvider.handle(UnpackedSyncFunctionSerializer.get().deserialize((List<QVariant>) data.data)); + break; + case RPCCALL: + busProvider.handle(UnpackedRpcCallFunctionSerializer.get().deserialize((List<QVariant>) data.data)); + break; + case INITREQUEST: + busProvider.handle(InitRequestFunctionSerializer.get().deserialize((List<QVariant>) data.data)); + break; + case INITDATA: + busProvider.handle(UnpackedInitDataFunctionSerializer.get().deserialize((List<QVariant>) data.data)); + break; + case HEARTBEAT: + busProvider.handle(HeartbeatSerializer.get().deserialize((List<QVariant>) data)); + break; + case HEARTBEATREPLY: + busProvider.handle(HeartbeatReplySerializer.get().deserialize((List<QVariant>) data)); + break; + default: + busProvider.sendEvent(new GeneralErrorEvent("Unknown package received: " + data)); + break; + } + } + } catch (Exception e) { + busProvider.sendEvent(new GeneralErrorEvent(e)); + } + } + } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java b/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java index 64928ad78..4d2d1f508 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java @@ -9,12 +9,15 @@ import java.util.Map; import de.kuschku.libquassel.exceptions.UnknownTypeException; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.objects.serializers.ObjectSerializer; +import de.kuschku.libquassel.syncables.serializers.AliasManagerSerializer; import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; import de.kuschku.libquassel.syncables.serializers.BufferViewConfigSerializer; import de.kuschku.libquassel.syncables.serializers.BufferViewManagerSerializer; import de.kuschku.libquassel.syncables.serializers.IdentitySerializer; +import de.kuschku.libquassel.syncables.serializers.IgnoreListManagerSerializer; import de.kuschku.libquassel.syncables.serializers.IrcChannelSerializer; import de.kuschku.libquassel.syncables.serializers.IrcUserSerializer; +import de.kuschku.libquassel.syncables.serializers.NetworkConfigSerializer; import de.kuschku.libquassel.syncables.serializers.NetworkSerializer; import de.kuschku.libquassel.syncables.types.SyncableObject; @@ -23,6 +26,7 @@ public class SyncableRegistry { private static final Map<String, ObjectSerializer<? extends SyncableObject>> map = new HashMap<>(); static { + map.put("IgnoreListManager", IgnoreListManagerSerializer.get()); map.put("BufferSyncer", BufferSyncerSerializer.get()); map.put("BufferViewConfig", BufferViewConfigSerializer.get()); map.put("BufferViewManager", BufferViewManagerSerializer.get()); @@ -30,6 +34,8 @@ public class SyncableRegistry { map.put("IrcChannel", IrcChannelSerializer.get()); map.put("IrcUser", IrcUserSerializer.get()); map.put("Network", NetworkSerializer.get()); + map.put("NetworkConfig", NetworkConfigSerializer.get()); + map.put("AliasManager", AliasManagerSerializer.get()); } private SyncableRegistry() { @@ -39,7 +45,7 @@ public class SyncableRegistry { @Nullable public static SyncableObject from(@NonNull InitDataFunction function) throws UnknownTypeException { ObjectSerializer<? extends SyncableObject> serializer = map.get(function.className); - if (serializer == null) throw new UnknownTypeException(function.className); + if (serializer == null) throw new UnknownTypeException(function.className, function); return serializer.from(function); } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/Synced.java b/app/src/main/java/de/kuschku/libquassel/syncables/Synced.java new file mode 100644 index 000000000..bd4e49d59 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/syncables/Synced.java @@ -0,0 +1,8 @@ +package de.kuschku.libquassel.syncables; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.SOURCE) +public @interface Synced { +} diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java new file mode 100644 index 000000000..2e089880b --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java @@ -0,0 +1,60 @@ +package de.kuschku.libquassel.syncables.serializers; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; +import java.util.Map; + +import de.kuschku.libquassel.functions.types.PackedFunction; +import de.kuschku.libquassel.functions.types.SerializedFunction; +import de.kuschku.libquassel.functions.types.UnpackedFunction; +import de.kuschku.libquassel.objects.serializers.ObjectSerializer; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.types.AliasManager; + +public class AliasManagerSerializer implements ObjectSerializer<AliasManager> { + @NonNull + private static final AliasManagerSerializer serializer = new AliasManagerSerializer(); + + @NonNull + public static AliasManagerSerializer get() { + return serializer; + } + + private AliasManagerSerializer() { + + } + + @Nullable + @Override + public QVariant<Map<String, QVariant>> toVariantMap(@NonNull AliasManager data) { + throw new IllegalArgumentException(); + } + + @NonNull + @Override + public AliasManager fromDatastream(@NonNull Map<String, QVariant> map) { + return fromLegacy(map); + } + + @NonNull + @Override + public AliasManager fromLegacy(@NonNull Map<String, QVariant> map) { + Map<String, QVariant<List<String>>> aliases = (Map<String, QVariant<List<String>>>) map.get("Aliases").data; + return new AliasManager( + aliases.get("names").data, + aliases.get("expansions").data + ); + } + + @Nullable + @Override + public AliasManager from(@NonNull SerializedFunction function) { + if (function instanceof PackedFunction) + return fromLegacy(((PackedFunction) function).getData()); + else if (function instanceof UnpackedFunction) + return fromDatastream(((UnpackedFunction) function).getData()); + else throw new IllegalArgumentException(); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferSyncerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferSyncerSerializer.java index 76866b898..786cf2644 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferSyncerSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferSyncerSerializer.java @@ -30,25 +30,22 @@ public class BufferSyncerSerializer implements ObjectSerializer<BufferSyncer> { @Nullable @Override public QVariant<Map<String, QVariant>> toVariantMap(@NonNull BufferSyncer data) { - // TODO: Implement this - return null; + // FIXME: IMPLEMENT + throw new IllegalArgumentException(); } @NonNull @Override public BufferSyncer fromDatastream(@NonNull Map<String, QVariant> map) { - return new BufferSyncer( - DatastreamPeer.unboxedListToMap((List<Integer>) map.get("LastSeenMsg").data), - DatastreamPeer.unboxedListToMap((List<Integer>) map.get("MarkerLines").data) - ); + return fromLegacy(map); } @NonNull @Override public BufferSyncer fromLegacy(@NonNull Map<String, QVariant> map) { return new BufferSyncer( - (Map<Integer, Integer>) map.get("LastSeenMsg").data, - (Map<Integer, Integer>) map.get("MarkerLines").data + DatastreamPeer.unboxedListToMap((List<Integer>) map.get("LastSeenMsg").data), + DatastreamPeer.unboxedListToMap((List<Integer>) map.get("LastSeenMsg").data) ); } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewManagerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewManagerSerializer.java index c73a286b0..e58dbb8b0 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewManagerSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewManagerSerializer.java @@ -29,7 +29,8 @@ public class BufferViewManagerSerializer implements ObjectSerializer<BufferViewM @Nullable @Override public QVariant<Map<String, QVariant>> toVariantMap(@NonNull BufferViewManager data) { - return null; + // FIXME: IMPLEMENT + throw new IllegalArgumentException(); } @NonNull diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java new file mode 100644 index 000000000..2a114c537 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java @@ -0,0 +1,63 @@ +package de.kuschku.libquassel.syncables.serializers; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; +import java.util.Map; + +import de.kuschku.libquassel.functions.types.PackedFunction; +import de.kuschku.libquassel.functions.types.SerializedFunction; +import de.kuschku.libquassel.functions.types.UnpackedFunction; +import de.kuschku.libquassel.objects.serializers.ObjectSerializer; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.types.IgnoreListManager; + +public class IgnoreListManagerSerializer implements ObjectSerializer<IgnoreListManager> { + private static IgnoreListManagerSerializer serializer = new IgnoreListManagerSerializer(); + public static IgnoreListManagerSerializer get() { + return serializer; + } + + private IgnoreListManagerSerializer() { + + } + + @Nullable + @Override + public QVariant<Map<String, QVariant>> toVariantMap(@NonNull IgnoreListManager data) { + // FIXME: IMPLEMENT + throw new IllegalArgumentException(); + } + + @NonNull + @Override + public IgnoreListManager fromDatastream(@NonNull Map<String, QVariant> map) { + return fromLegacy(map); + } + + @NonNull + @Override + public IgnoreListManager fromLegacy(@NonNull Map<String, QVariant> map) { + Map<String, QVariant> internalMap = (Map<String, QVariant>) map.get("IgnoreList").data; + return new IgnoreListManager( + (List<Integer>) internalMap.get("scope").data, + (List<Integer>) internalMap.get("ignoreType").data, + (List<Boolean>) internalMap.get("isActive").data, + (List<String>) internalMap.get("scopeRule").data, + (List<Boolean>) internalMap.get("isRegEx").data, + (List<Integer>) internalMap.get("strictness").data, + (List<String>) internalMap.get("ignoreRule").data + ); + } + + @Nullable + @Override + public IgnoreListManager from(@NonNull SerializedFunction function) { + if (function instanceof PackedFunction) + return fromLegacy(((PackedFunction) function).getData()); + else if (function instanceof UnpackedFunction) + return fromDatastream(((UnpackedFunction) function).getData()); + else throw new IllegalArgumentException(); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcChannelSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcChannelSerializer.java index 7bfcb5527..7505909f3 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcChannelSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcChannelSerializer.java @@ -30,12 +30,12 @@ public class IrcChannelSerializer implements ObjectSerializer<IrcChannel> { @Override public QVariant<Map<String, QVariant>> toVariantMap(@NonNull IrcChannel data) { final QVariant<Map<String, QVariant>> map = new QVariant<>(new HashMap<>()); - map.data.put("name", new QVariant<>(data.name)); - map.data.put("topic", new QVariant<>(data.topic)); - map.data.put("password", new QVariant<>(data.password)); - map.data.put("UserModes", StringObjectMapSerializer.<String>get().toVariantMap(data.UserModes)); - map.data.put("ChanModes", new QVariant<>(data.ChanModes)); - map.data.put("encrypted", new QVariant<>(data.encrypted)); + map.data.put("name", new QVariant<>(data.getName())); + map.data.put("topic", new QVariant<>(data.getTopic())); + map.data.put("password", new QVariant<>(data.getPassword())); + map.data.put("UserModes", StringObjectMapSerializer.<String>get().toVariantMap(data.getUserModes())); + map.data.put("ChanModes", new QVariant<>(data.getChanModes())); + map.data.put("encrypted", new QVariant<>(data.isEncrypted())); return map; } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcUserSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcUserSerializer.java index 5004534e1..d5fd53e02 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcUserSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcUserSerializer.java @@ -36,22 +36,22 @@ public class IrcUserSerializer implements ObjectSerializer<IrcUser> { final QVariant<Map<String, QVariant>> map = new QVariant<>(new HashMap<>()); assertNotNull(map.data); - map.data.put("server", new QVariant<>(data.server)); - map.data.put("ircOperator", new QVariant<>(data.ircOperator)); - map.data.put("away", new QVariant<>(data.away)); - map.data.put("lastAwayMessage", new QVariant<>(data.lastAwayMessage)); - map.data.put("idleTime", new QVariant<>(data.idleTime)); - map.data.put("whoisServiceReply", new QVariant<>(data.whoisServiceReply)); - map.data.put("suserHost", new QVariant<>(data.suserHost)); - map.data.put("nick", new QVariant<>(data.nick)); - map.data.put("realName", new QVariant<>(data.realName)); - map.data.put("awayMessage", new QVariant<>(data.awayMessage)); - map.data.put("loginTime", new QVariant<>(data.loginTime)); - map.data.put("encrypted", new QVariant<>(data.encrypted)); - map.data.put("channels", new QVariant<>(data.channels)); - map.data.put("host", new QVariant<>(data.host)); - map.data.put("userModes", new QVariant<>(data.userModes)); - map.data.put("user", new QVariant<>(data.user)); + map.data.put("server", new QVariant<>(data.getServer())); + map.data.put("ircOperator", new QVariant<>(data.getIrcOperator())); + map.data.put("away", new QVariant<>(data.isAway())); + map.data.put("lastAwayMessage", new QVariant<>(data.getLastAwayMessage())); + map.data.put("idleTime", new QVariant<>(data.getIdleTime())); + map.data.put("whoisServiceReply", new QVariant<>(data.getWhoisServiceReply())); + map.data.put("suserHost", new QVariant<>(data.getSuserHost())); + map.data.put("nick", new QVariant<>(data.getNick())); + map.data.put("realName", new QVariant<>(data.getRealName())); + map.data.put("awayMessage", new QVariant<>(data.getAwayMessage())); + map.data.put("loginTime", new QVariant<>(data.getLoginTime())); + map.data.put("encrypted", new QVariant<>(data.isEncrypted())); + map.data.put("channels", new QVariant<>(data.getChannels())); + map.data.put("host", new QVariant<>(data.getHost())); + map.data.put("userModes", new QVariant<>(data.getUserModes())); + map.data.put("user", new QVariant<>(data.getUser())); return map; } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java new file mode 100644 index 000000000..277b08eaf --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java @@ -0,0 +1,63 @@ +package de.kuschku.libquassel.syncables.serializers; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Map; + +import de.kuschku.libquassel.functions.types.PackedFunction; +import de.kuschku.libquassel.functions.types.SerializedFunction; +import de.kuschku.libquassel.functions.types.UnpackedFunction; +import de.kuschku.libquassel.objects.serializers.ObjectSerializer; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.types.NetworkConfig; +import de.kuschku.libquassel.syncables.types.SyncableObject; + +public class NetworkConfigSerializer implements ObjectSerializer<NetworkConfig> { + private static NetworkConfigSerializer serializer = new NetworkConfigSerializer(); + public static NetworkConfigSerializer get() { + return serializer; + } + + private NetworkConfigSerializer() { + + } + + @Nullable + @Override + public QVariant<Map<String, QVariant>> toVariantMap(@NonNull NetworkConfig data) { + // FIXME: IMPLEMENT + throw new IllegalArgumentException(); + } + + @NonNull + @Override + public NetworkConfig fromDatastream(@NonNull Map<String, QVariant> map) { + return fromLegacy(map); + } + + @NonNull + @Override + public NetworkConfig fromLegacy(@NonNull Map<String, QVariant> map) { + return new NetworkConfig( + (int) map.get("autoWhoNickLimit").data, + (int) map.get("autoWhoDelay").data, + (boolean) map.get("autoWhoEnabled").data, + (boolean) map.get("standardCtcp").data, + (int) map.get("pingInterval").data, + (int) map.get("autoWhoInterval").data, + (int) map.get("maxPingCount").data, + (boolean) map.get("pingTimeoutEnabled").data + ); + } + + @Nullable + @Override + public NetworkConfig from(@NonNull SerializedFunction function) { + if (function instanceof PackedFunction) + return fromLegacy(((PackedFunction) function).getData()); + else if (function instanceof UnpackedFunction) + return fromDatastream(((UnpackedFunction) function).getData()); + else throw new IllegalArgumentException(); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkSerializer.java index 2c395dc26..4d19448ba 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkSerializer.java @@ -37,8 +37,8 @@ public class NetworkSerializer implements ObjectSerializer<Network> { @Nullable @Override public QVariant<Map<String, QVariant>> toVariantMap(@NonNull Network data) { - // FIXME: Implement this - return null; + // FIXME: IMPLEMENT + throw new IllegalArgumentException(); } @NonNull @@ -51,7 +51,7 @@ public class NetworkSerializer implements ObjectSerializer<Network> { final Map<String, IrcChannel> channelMap = new HashMap<>(channels.size()); for (IrcChannel channel : channels) { - channelMap.put(channel.name, channel); + channelMap.put(channel.getName(), channel); } final Map<String, IrcUser> userMap = new HashMap<>(users.size()); @@ -87,6 +87,9 @@ public class NetworkSerializer implements ObjectSerializer<Network> { for (IrcUser user : users) { user.setNetwork(network); } + for (IrcChannel channel : channels) { + channel.setNetwork(network); + } return network; } @@ -154,12 +157,12 @@ public class NetworkSerializer implements ObjectSerializer<Network> { final Map<String, IrcChannel> channels = new HashMap<>(wrappedChannels.size()); for (Map.Entry<String, QVariant<Map<String, QVariant>>> entry : wrappedChannels.entrySet()) { final IrcChannel ircChannel = IrcChannelSerializer.get().fromLegacy(entry.getValue().data); - channels.put(ircChannel.name, ircChannel); + channels.put(ircChannel.getName(), ircChannel); } final Map<String, IrcUser> users = new HashMap<>(wrappedUsers.size()); for (Map.Entry<String, QVariant<Map<String, QVariant>>> entry : wrappedUsers.entrySet()) { final IrcUser ircUser = IrcUserSerializer.get().fromLegacy(entry.getValue().data); - users.put(ircUser.nick, ircUser); + users.put(ircUser.getNick(), ircUser); } final Map<String, String> supports = StringObjectMapSerializer.<String>get().fromLegacy((Map<String, QVariant>) map.get("Supports").data); Network network = new Network( diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/AliasManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/AliasManager.java new file mode 100644 index 000000000..4236fc778 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/AliasManager.java @@ -0,0 +1,38 @@ +package de.kuschku.libquassel.syncables.types; + +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.Map; + +import de.kuschku.libquassel.BusProvider; +import de.kuschku.libquassel.Client; +import de.kuschku.libquassel.functions.types.InitDataFunction; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.serializers.AliasManagerSerializer; + +public class AliasManager extends SyncableObject<AliasManager> { + public List<String> names; + public List<String> expansions; + + public AliasManager(List<String> names, List<String> expansions) { + this.names = names; + this.expansions = expansions; + } + + @Override + public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) { + + } + + @Override + public void update(AliasManager from) { + names = from.names; + expansions = from.expansions; + } + + @Override + public void update(Map<String, QVariant> from) { + update(AliasManagerSerializer.get().fromDatastream(from)); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java index 20d24a33f..40cae47dc 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java @@ -10,15 +10,18 @@ import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.message.Message; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.serializers.AliasManagerSerializer; +import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; import de.kuschku.util.observables.lists.ObservableSortedList; import static de.kuschku.util.AndroidAssert.assertNotNull; -public class BufferSyncer extends SyncableObject { +public class BufferSyncer extends SyncableObject<BufferSyncer> { @NonNull - private final SparseIntArray LastSeenMsg = new SparseIntArray(); + private SparseIntArray LastSeenMsg = new SparseIntArray(); @NonNull - private final SparseIntArray MarkerLines = new SparseIntArray(); + private SparseIntArray MarkerLines = new SparseIntArray(); private Client client; @@ -41,6 +44,17 @@ public class BufferSyncer extends SyncableObject { setClient(client); } + @Override + public void update(BufferSyncer from) { + LastSeenMsg = from.LastSeenMsg; + MarkerLines = from.MarkerLines; + } + + @Override + public void update(Map<String, QVariant> from) { + update(BufferSyncerSerializer.get().fromDatastream(from)); + } + public void markBufferAsRead(@IntRange(from = 0) int bufferId) { ObservableSortedList<Message> buffer = client.getBacklogManager().get(bufferId); assertNotNull(buffer); @@ -69,4 +83,9 @@ public class BufferSyncer extends SyncableObject { public int getMarkerLine(int bufferId) { return MarkerLines.get(bufferId, -1); } + + public void removeBuffer(int bufferId) { + LastSeenMsg.put(bufferId, -1); + MarkerLines.put(bufferId, -1); + } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java index 90844af33..b0fa90823 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java @@ -1,21 +1,31 @@ package de.kuschku.libquassel.syncables.types; +import android.net.*; import android.support.annotation.NonNull; +import java.util.Collections; import java.util.List; +import java.util.Map; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; +import de.kuschku.libquassel.syncables.serializers.BufferViewConfigSerializer; +import de.kuschku.util.AndroidAssert; import de.kuschku.util.observables.callbacks.ElementCallback; import de.kuschku.util.observables.lists.IObservableList; import de.kuschku.util.observables.lists.ObservableElementList; -public class BufferViewConfig extends SyncableObject { +import static de.kuschku.util.AndroidAssert.*; + +public class BufferViewConfig extends SyncableObject<BufferViewConfig> { private String bufferViewName; private List<Integer> TemporarilyRemovedBuffers; private boolean hideInactiveNetworks; private IObservableList<ElementCallback<Integer>, Integer> BufferList; + private IObservableList<ElementCallback<Integer>, Integer> NetworkList = new ObservableElementList<>(); private int allowedBufferTypes; private boolean sortAlphabetically; private boolean disableDecoration; @@ -24,12 +34,13 @@ public class BufferViewConfig extends SyncableObject { private int minimumActivity; private boolean hideInactiveBuffers; private List<Integer> RemovedBuffers; + private Client client; public BufferViewConfig(String bufferViewName, List<Integer> temporarilyRemovedBuffers, boolean hideInactiveNetworks, @NonNull List<Integer> bufferList, int allowedBufferTypes, boolean sortAlphabetically, boolean disableDecoration, boolean addNewBuffersAutomatically, int networkId, int minimumActivity, boolean hideInactiveBuffers, List<Integer> removedBuffers) { this.bufferViewName = bufferViewName; - TemporarilyRemovedBuffers = temporarilyRemovedBuffers; + this.TemporarilyRemovedBuffers = temporarilyRemovedBuffers; this.hideInactiveNetworks = hideInactiveNetworks; - BufferList = new ObservableElementList<>(bufferList); + this.BufferList = new ObservableElementList<>(bufferList); this.allowedBufferTypes = allowedBufferTypes; this.sortAlphabetically = sortAlphabetically; this.disableDecoration = disableDecoration; @@ -37,7 +48,7 @@ public class BufferViewConfig extends SyncableObject { this.networkId = networkId; this.minimumActivity = minimumActivity; this.hideInactiveBuffers = hideInactiveBuffers; - RemovedBuffers = removedBuffers; + this.RemovedBuffers = removedBuffers; } public String getBufferViewName() { @@ -114,6 +125,11 @@ public class BufferViewConfig extends SyncableObject { public void setNetworkId(int networkId) { this.networkId = networkId; + if (this.networkId != -1) { + this.NetworkList.addAll(client.getNetworks()); + } else { + this.NetworkList.retainAll(Collections.singletonList(networkId)); + } } public int getMinimumActivity() { @@ -161,10 +177,33 @@ public class BufferViewConfig extends SyncableObject { @Override public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) { + this.client = client; setObjectName(function.objectName); + assertNotNull(client.getBufferViewManager()); client.getBufferViewManager().BufferViews.put(Integer.valueOf(function.objectName), this); } + @Override + public void update(BufferViewConfig from) { + this.bufferViewName = from.bufferViewName; + this.TemporarilyRemovedBuffers = from.TemporarilyRemovedBuffers; + this.hideInactiveNetworks = from.hideInactiveNetworks; + this.BufferList = from.BufferList; + this.allowedBufferTypes = from.allowedBufferTypes; + this.sortAlphabetically = from.sortAlphabetically; + this.disableDecoration = from.disableDecoration; + this.addNewBuffersAutomatically = from.addNewBuffersAutomatically; + this.networkId = from.networkId; + this.minimumActivity = from.minimumActivity; + this.hideInactiveBuffers = from.hideInactiveBuffers; + this.RemovedBuffers = from.RemovedBuffers; + } + + @Override + public void update(Map<String, QVariant> from) { + update(BufferViewConfigSerializer.get().fromDatastream(from)); + } + public void addBuffer(int bufferId, int position) { BufferList.add(position, bufferId); } @@ -182,4 +221,15 @@ public class BufferViewConfig extends SyncableObject { removeBuffer(bufferId); sync("removeBuffer", new Object[]{bufferId}); } + + @NonNull + public IObservableList<ElementCallback<Integer>, Integer> getNetworkList() { + return NetworkList; + } + + public void doLateInit() { + NetworkList.clear(); + // This should initialize the network list + setNetworkId(getNetworkId()); + } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java index 863443d9c..c6e927881 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java @@ -9,10 +9,14 @@ import java.util.Map; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; +import de.kuschku.libquassel.syncables.serializers.BufferViewManagerSerializer; -public class BufferViewManager extends SyncableObject { +public class BufferViewManager extends SyncableObject<BufferViewManager> { @NonNull - public final Map<Integer, BufferViewConfig> BufferViews = new HashMap<>(); + public Map<Integer, BufferViewConfig> BufferViews = new HashMap<>(); + private Client client; public BufferViewManager(@NonNull List<Integer> BufferViewIds) { for (int i : BufferViewIds) { @@ -30,7 +34,21 @@ public class BufferViewManager extends SyncableObject { @Override public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) { + this.client = client; setObjectName(function.objectName); client.setBufferViewManager(this); } + + @Override + public void update(BufferViewManager from) { + this.BufferViews = from.BufferViews; + for (int id : BufferViews.keySet()) { + client.sendInitRequest("BufferViewConfig", String.valueOf(id)); + } + } + + @Override + public void update(Map<String, QVariant> from) { + update(BufferViewManagerSerializer.get().fromDatastream(from)); + } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java index cfb0048a0..dc065d339 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java @@ -3,13 +3,17 @@ package de.kuschku.libquassel.syncables.types; import android.support.annotation.NonNull; import java.util.List; +import java.util.Map; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; +import de.kuschku.libquassel.primitives.types.QVariant; import de.kuschku.libquassel.syncables.Syncable; +import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; +import de.kuschku.libquassel.syncables.serializers.IdentitySerializer; -public class Identity extends SyncableObject { +public class Identity extends SyncableObject<Identity> { @Syncable private String identityName; @Syncable @@ -257,4 +261,14 @@ public class Identity extends SyncableObject { public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) { } + + @Override + public void update(Identity from) { + + } + + @Override + public void update(Map<String, QVariant> from) { + update(IdentitySerializer.get().fromDatastream(from)); + } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java new file mode 100644 index 000000000..8904263a1 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java @@ -0,0 +1,150 @@ +package de.kuschku.libquassel.syncables.types; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import de.kuschku.libquassel.BusProvider; +import de.kuschku.libquassel.Client; +import de.kuschku.libquassel.functions.types.InitDataFunction; +import de.kuschku.libquassel.message.Message; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; +import de.kuschku.libquassel.syncables.serializers.IgnoreListManagerSerializer; + +import static de.kuschku.util.AndroidAssert.assertEquals; + +public class IgnoreListManager extends SyncableObject<IgnoreListManager> { + List<IgnoreRule> ignoreRules = new ArrayList<>(); + + public IgnoreListManager(List<Integer> scope, List<Integer> ignoreType, + List<Boolean> isActive, List<String> scopeRule, List<Boolean> isRegEx, + List<Integer> strictness, List<String> ignoreRule) { + assertEquals(scope.size(),ignoreType.size(), isActive.size(),scopeRule.size(), isRegEx.size(), strictness.size(), ignoreRule.size()); + + for (int i = 0; i < scope.size(); i++) { + ignoreRules.add(new IgnoreRule( + scope.get(i), + ignoreType.get(i), + isActive.get(i), + scopeRule.get(i), + isRegEx.get(i), + strictness.get(i), + ignoreRule.get(i) + )); + } + } + + @Override + public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) { + client.setIgnoreListManager(this); + } + + @Override + public void update(IgnoreListManager from) { + + } + + @Override + public void update(Map<String, QVariant> from) { + update(IgnoreListManagerSerializer.get().fromDatastream(from)); + } + + public boolean matches(Message message) { + return false; + } + + public static class IgnoreRule { + private Scope scope; + private Type ignoreType; + private boolean isActive; + private String scopeRule; + private boolean isRegEx; + private Strictness strictness; + private String ignoreRule; + + public IgnoreRule(Integer scope, Integer ignoreType, boolean isActive, String scopeRule, boolean isRegEx, Integer strictness, String ignoreRule) { + this( + Scope.of(scope), + Type.of(ignoreType), + isActive, + scopeRule, + isRegEx, + Strictness.of(strictness), + ignoreRule + ); + } + + public IgnoreRule(Scope scope, Type ignoreType, boolean isActive, String scopeRule, boolean isRegEx, Strictness strictness, String ignoreRule) { + this.scope = scope; + this.ignoreType = ignoreType; + this.isActive = isActive; + this.scopeRule = scopeRule; + this.isRegEx = isRegEx; + this.strictness = strictness; + this.ignoreRule = ignoreRule; + } + + public enum Strictness { + INVALID(-1), + UNMATCHED(0), + SOFT(1), + HARD(2); + + public final int id; + Strictness(int id) { + this.id = id; + } + public static Strictness of(int id) { + switch (id) { + case 0: return UNMATCHED; + case 1: return SOFT; + case 2: return HARD; + default: return INVALID; + } + } + } + + public enum Type { + INVALID(-1), + SENDER_IGNORE(0), + MESSAGE_IGNORE(1), + CTCP_IGNORE(2); + + public final int id; + Type(int id) { + this.id = id; + } + public static Type of(int id) { + switch (id) { + case 0: return SENDER_IGNORE; + case 1: return MESSAGE_IGNORE; + case 2: return CTCP_IGNORE; + default: return INVALID; + } + } + } + + public enum Scope { + INVALID(-1), + GLOBAL_SCOPE(0), + NETWORK_SCOPE(1), + CHANNEL_SCOPE(2); + + public final int id; + Scope(int id) { + this.id = id; + } + public static Scope of(int id) { + switch (id) { + case 0: return GLOBAL_SCOPE; + case 1: return NETWORK_SCOPE; + case 2: return CHANNEL_SCOPE; + default: return INVALID; + } + } + } + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java index 88ce98da9..853a559cb 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java @@ -1,21 +1,38 @@ package de.kuschku.libquassel.syncables.types; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.Synced; +import de.kuschku.libquassel.syncables.serializers.IrcChannelSerializer; +import de.kuschku.util.AndroidAssert; -public class IrcChannel extends SyncableObject { - public final String name; - public final String topic; - public final String password; - public final Map<String, String> UserModes; - public final Map<String, Object> ChanModes; - public final boolean encrypted; +import static de.kuschku.util.AndroidAssert.*; + +public class IrcChannel extends SyncableObject<IrcChannel> { + @Synced private String name; + @Synced private String topic; + @Synced private String password; + @Synced private Map<String, String> UserModes; + @Synced private Map<String, Object> ChanModes; + @Synced private boolean encrypted; + + @Nullable + private Network network; public IrcChannel(String name, String topic, String password, Map<String, String> userModes, Map<String, Object> chanModes, boolean encrypted) { @@ -27,6 +44,26 @@ public class IrcChannel extends SyncableObject { this.encrypted = encrypted; } + public Map<String, List<String>> getA_ChanModes() { + if (ChanModes.get("A") == null) ChanModes.put("A", new HashMap<>()); + return (Map<String, List<String>>) ChanModes.get("A"); + } + + public Map<String, String> getB_ChanModes() { + if (ChanModes.get("B") == null) ChanModes.put("B", new HashMap<>()); + return (Map<String, String>) ChanModes.get("B"); + } + + public Map<String, String> getC_ChanModes() { + if (ChanModes.get("C") == null) ChanModes.put("C", new HashMap<>()); + return (Map<String, String>) ChanModes.get("C"); + } + + public Set<String> getD_ChanModes() { + if (ChanModes.get("D") == null) ChanModes.put("D", new HashSet<>()); + return (Set<String>) ChanModes.get("D"); + } + @NonNull @Override public String toString() { @@ -40,6 +77,15 @@ public class IrcChannel extends SyncableObject { '}'; } + @Nullable + public Network getNetwork() { + return network; + } + + public void setNetwork(@Nullable Network network) { + this.network = network; + } + public void joinIrcUsers(@NonNull List<String> users, @NonNull List<String> modes) { for (int i = 0; i < users.size(); i++) { joinIrcUser(users.get(i), modes.get(i)); @@ -70,13 +116,158 @@ public class IrcChannel extends SyncableObject { UserModes.put(nick, UserModes.get(nick).replace(mode, "")); } + public void addChannelMode(Character mode, String params) { + addChannelMode(String.copyValueOf(new char[]{mode}), params); + } + public void addChannelMode(char mode, String params) { + addChannelMode(String.copyValueOf(new char[]{mode}), params); + } + public void addChannelMode(String mode, String params) { + assertNotNull(network); + + Network.ChannelModeType type = network.channelModeType(mode); + switch (type) { + case NOT_A_CHANMODE: + return; + case A_CHANMODE: + if (!getA_ChanModes().containsKey(mode)) { + getA_ChanModes().put(mode, new ArrayList<>(Collections.singleton(params))); + } else { + getA_ChanModes().get(mode).add(params); + } + break; + case B_CHANMODE: + getB_ChanModes().put(mode, params); + break; + case C_CHANMODE: + getB_ChanModes().put(mode, params); + break; + case D_CHANMODE: + + getD_ChanModes().add(mode); + break; + } + } + public void removeChannelMode(Character mode, String params) { + removeChannelMode(String.copyValueOf(new char[]{mode}), params); + } + public void removeChannelMode(char mode, String params) { + removeChannelMode(String.copyValueOf(new char[]{mode}), params); + } + public void removeChannelMode(String mode, String params) { + assertNotNull(network); + + Network.ChannelModeType type = network.channelModeType(mode); + switch (type) { + case NOT_A_CHANMODE: + return; + case A_CHANMODE: + if (getA_ChanModes().containsKey(mode)) + getA_ChanModes().get(mode).removeAll(Collections.singleton(params)); + break; + case B_CHANMODE: + getB_ChanModes().remove(mode); + break; + case C_CHANMODE: + getB_ChanModes().remove(mode); + break; + case D_CHANMODE: + getB_ChanModes().remove(mode); + break; + } + } + + public boolean hasMode(Character mode) { + return hasMode(String.copyValueOf(new char[]{mode})); + } + public boolean hasMode(char mode) { + return hasMode(String.copyValueOf(new char[]{mode})); + } + public boolean hasMode(String mode) { + assertNotNull(network); + + Network.ChannelModeType type = network.channelModeType(mode); + switch (type) { + case A_CHANMODE: + return getA_ChanModes().containsKey(mode); + case B_CHANMODE: + return getA_ChanModes().containsKey(mode); + case C_CHANMODE: + return getA_ChanModes().containsKey(mode); + case D_CHANMODE: + return getA_ChanModes().containsKey(mode); + + default: + return false; + } + } + public void renameUser(String oldNick, String newNick) { UserModes.put(newNick, UserModes.get(oldNick)); UserModes.remove(oldNick); } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Map<String, String> getUserModes() { + return UserModes; + } + + public void setUserModes(Map<String, String> userModes) { + UserModes = userModes; + } + + public boolean isEncrypted() { + return encrypted; + } + + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } + @Override public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) { } + + @Override + public void update(IrcChannel from) { + this.name = from.name; + this.topic = from.topic; + this.password = from.password; + this.UserModes = from.UserModes; + this.ChanModes = from.ChanModes; + this.encrypted = from.encrypted; + } + + @Override + public void update(Map<String, QVariant> from) { + update(IrcChannelSerializer.get().fromDatastream(from)); + } + + public Map<String, Object> getChanModes() { + return ChanModes; + } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java index b13a94d86..2aa1d62ab 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java @@ -6,31 +6,35 @@ import android.support.annotation.Nullable; import org.joda.time.DateTime; import java.util.List; +import java.util.Map; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; +import de.kuschku.libquassel.syncables.serializers.IrcUserSerializer; import static de.kuschku.util.AndroidAssert.assertNotNull; -public class IrcUser extends SyncableObject { - public String server; - public String ircOperator; - public boolean away; - public int lastAwayMessage; - public DateTime idleTime; - public String whoisServiceReply; - public String suserHost; - public String nick; - public String realName; - public String awayMessage; - public DateTime loginTime; - public boolean encrypted; +public class IrcUser extends SyncableObject<IrcUser> { + private String server; + private String ircOperator; + private boolean away; + private int lastAwayMessage; + private DateTime idleTime; + private String whoisServiceReply; + private String suserHost; + private String nick; + private String realName; + private String awayMessage; + private DateTime loginTime; + private boolean encrypted; @NonNull - public List<String> channels; - public String host; - public String userModes; - public String user; + private List<String> channels; + private String host; + private String userModes; + private String user; private Network network; @@ -56,6 +60,71 @@ public class IrcUser extends SyncableObject { this.user = user; } + public String getServer() { + return server; + } + + public String getIrcOperator() { + return ircOperator; + } + + public boolean isAway() { + return away; + } + + public int getLastAwayMessage() { + return lastAwayMessage; + } + + public DateTime getIdleTime() { + return idleTime; + } + + public String getWhoisServiceReply() { + return whoisServiceReply; + } + + public String getSuserHost() { + return suserHost; + } + + public String getNick() { + return nick; + } + + public String getRealName() { + return realName; + } + + public String getAwayMessage() { + return awayMessage; + } + + public DateTime getLoginTime() { + return loginTime; + } + + public boolean isEncrypted() { + return encrypted; + } + + @NonNull + public List<String> getChannels() { + return channels; + } + + public String getHost() { + return host; + } + + public String getUserModes() { + return userModes; + } + + public String getUser() { + return user; + } + /* BEGIN SYNC */ public void setServer(String server) { @@ -155,6 +224,31 @@ public class IrcUser extends SyncableObject { setNetwork(network); } + @Override + public void update(IrcUser from) { + this.server = from.server; + this.ircOperator = from.ircOperator; + this.away = from.away; + this.lastAwayMessage = from.lastAwayMessage; + this.idleTime = from.idleTime; + this.whoisServiceReply = from.whoisServiceReply; + this.suserHost = from.suserHost; + this.nick = from.nick; + this.realName = from.realName; + this.awayMessage = from.awayMessage; + this.loginTime = from.loginTime; + this.encrypted = from.encrypted; + this.channels = from.channels; + this.host = from.host; + this.userModes = from.userModes; + this.user = from.user; + } + + @Override + public void update(Map<String, QVariant> from) { + update(IrcUserSerializer.get().fromDatastream(from)); + } + public void quit() { network.quit(this.nick); } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java index 375b6b93c..834955523 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java @@ -14,14 +14,15 @@ import java.util.Set; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; -import de.kuschku.libquassel.functions.types.InitRequestFunction; import de.kuschku.libquassel.localtypes.Buffer; import de.kuschku.libquassel.objects.types.NetworkServer; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.serializers.NetworkSerializer; import de.kuschku.util.observables.ContentComparable; import static de.kuschku.util.AndroidAssert.assertNotNull; -public class Network extends SyncableObject implements ContentComparable<Network> { +public class Network extends SyncableObject<Network> implements ContentComparable<Network> { @NonNull private final Set<Buffer> buffers = new HashSet<>(); @NonNull @@ -76,6 +77,7 @@ public class Network extends SyncableObject implements ContentComparable<Network @Nullable private Map<String, IrcMode> supportedModes; private int networkId; + private Client client; public Network(@NonNull Map<String, IrcChannel> channels, @NonNull Map<String, IrcUser> users, @NonNull List<NetworkServer> serverList, @NonNull Map<String, String> supports, @@ -124,7 +126,7 @@ public class Network extends SyncableObject implements ContentComparable<Network assertNotNull(provider); for (IrcUser user : getUsers().values()) { - provider.dispatch(new InitRequestFunction("IrcUser", getNetworkId() + "/" + user.nick)); + client.sendInitRequest("IrcUser", getNetworkId() + "/" + user.getNick()); } } @@ -154,7 +156,7 @@ public class Network extends SyncableObject implements ContentComparable<Network } public void addIrcUser(String sender) { - provider.dispatch(new InitRequestFunction("IrcUser", getObjectName() + "/" + sender)); + client.sendInitRequest("IrcUser", getObjectName() + "/" + sender); } @Nullable @@ -194,6 +196,19 @@ public class Network extends SyncableObject implements ContentComparable<Network return channels; } + public void addIrcChannel(String channelName) { + IrcChannel ircChannel = new IrcChannel( + channelName, + null, + null, + new HashMap<>(), + new HashMap<>(), + false + ); + ircChannel.setNetwork(this); + channels.put(channelName, ircChannel); + } + public void setChannels(@NonNull Map<String, IrcChannel> channels) { this.channels = channels; } @@ -476,13 +491,57 @@ public class Network extends SyncableObject implements ContentComparable<Network @Override public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) { setObjectName(function.objectName); - setBusProvider(provider); setNetworkId(Integer.parseInt(function.objectName)); + setBusProvider(provider); + setClient(client); + doInit(); + } + + @Override + public void doInit() { getBuffers().addAll(client.getBuffers(getNetworkId())); initUsers(); client.putNetwork(this); } + @Override + public void update(Network from) { + this.channels = from.channels; + this.users = from.users; + this.ServerList = from.ServerList; + this.Supports = from.Supports; + this.autoIdentifyPassword = from.autoIdentifyPassword; + this.autoIdentifyService = from.autoIdentifyService; + this.autoReconnectInterval = from.autoReconnectInterval; + this.autoReconnectRetries = from.autoReconnectRetries; + this.codecForDecoding = from.codecForDecoding; + this.codecForEncoding = from.codecForEncoding; + this.codecForServer = from.codecForServer; + this.connectionState = from.connectionState; + this.currentServer = from.currentServer; + this.identityId = from.identityId; + this.isConnected = from.isConnected; + this.latency = from.latency; + this.myNick = from.myNick; + this.networkName = from.networkName; + this.perform = from.perform; + this.rejoinChannels = from.rejoinChannels; + this.saslAccount = from.saslAccount; + this.saslPassword = from.saslPassword; + this.unlimitedReconnectRetries = from.unlimitedReconnectRetries; + this.useAutoIdentify = from.useAutoIdentify; + this.useAutoReconnect = from.useAutoReconnect; + this.useRandomServer = from.useRandomServer; + this.useSasl = from.useSasl; + parsePrefix(); + assertNotNull(supportedModes); + } + + @Override + public void update(Map<String, QVariant> from) { + update(NetworkSerializer.get().fromDatastream(from)); + } + @Override public boolean equalsContent(@NonNull Network other) { return networkId == other.networkId; @@ -493,6 +552,10 @@ public class Network extends SyncableObject implements ContentComparable<Network return networkId - another.networkId; } + public void setClient(Client client) { + this.client = client; + } + public static class IrcMode { public final int rank; public final String prefix; @@ -511,4 +574,42 @@ public class Network extends SyncableObject implements ContentComparable<Network '}'; } } + + public ChannelModeType channelModeType(char mode) { + return channelModeType(String.copyValueOf(new char[]{mode})); + } + + public ChannelModeType channelModeType(String mode) { + if (mode.isEmpty()) + return ChannelModeType.NOT_A_CHANMODE; + + String rawChanModes = getSupports().get("CHANMODES"); + if (rawChanModes == null || rawChanModes.isEmpty()) + return ChannelModeType.NOT_A_CHANMODE; + + String[] chanModes = rawChanModes.split(","); + for (int i = 0; i < chanModes.length; i++) { + if (chanModes[i].contains(mode)) { + switch (i) { + case 0: return ChannelModeType.A_CHANMODE; + case 1: return ChannelModeType.B_CHANMODE; + case 2: return ChannelModeType.C_CHANMODE; + case 3: return ChannelModeType.D_CHANMODE; + default: return ChannelModeType.NOT_A_CHANMODE; + } + } + } + return ChannelModeType.NOT_A_CHANMODE; + } + + // see: + // http://www.irc.org/tech_docs/005.html + // http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt + public enum ChannelModeType { + NOT_A_CHANMODE, + A_CHANMODE, + B_CHANMODE, + C_CHANMODE, + D_CHANMODE + } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java new file mode 100644 index 000000000..1540f2994 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java @@ -0,0 +1,120 @@ +package de.kuschku.libquassel.syncables.types; + +import android.support.annotation.NonNull; + +import java.util.Map; + +import de.kuschku.libquassel.BusProvider; +import de.kuschku.libquassel.Client; +import de.kuschku.libquassel.functions.types.InitDataFunction; +import de.kuschku.libquassel.primitives.types.QVariant; +import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; +import de.kuschku.libquassel.syncables.serializers.NetworkConfigSerializer; + +public class NetworkConfig extends SyncableObject<NetworkConfig> { + private int autoWhoNickLimit; + private int autoWhoDelay; + private boolean autoWhoEnabled; + private boolean standardCtcp; + private int pingInterval; + private int autoWhoInterval; + private int maxPingCount; + private boolean pingTimeoutEnabled; + + public NetworkConfig(int autoWhoNickLimit, int autoWhoDelay, boolean autoWhoEnabled, boolean standardCtcp, int pingInterval, int autoWhoInterval, int maxPingCount, boolean pingTimeoutEnabled) { + this.autoWhoNickLimit = autoWhoNickLimit; + this.autoWhoDelay = autoWhoDelay; + this.autoWhoEnabled = autoWhoEnabled; + this.standardCtcp = standardCtcp; + this.pingInterval = pingInterval; + this.autoWhoInterval = autoWhoInterval; + this.maxPingCount = maxPingCount; + this.pingTimeoutEnabled = pingTimeoutEnabled; + } + + public int getAutoWhoNickLimit() { + return autoWhoNickLimit; + } + + public void setAutoWhoNickLimit(int autoWhoNickLimit) { + this.autoWhoNickLimit = autoWhoNickLimit; + } + + public int getAutoWhoDelay() { + return autoWhoDelay; + } + + public void setAutoWhoDelay(int autoWhoDelay) { + this.autoWhoDelay = autoWhoDelay; + } + + public boolean isAutoWhoEnabled() { + return autoWhoEnabled; + } + + public void setAutoWhoEnabled(boolean autoWhoEnabled) { + this.autoWhoEnabled = autoWhoEnabled; + } + + public boolean isStandardCtcp() { + return standardCtcp; + } + + public void setStandardCtcp(boolean standardCtcp) { + this.standardCtcp = standardCtcp; + } + + public int getPingInterval() { + return pingInterval; + } + + public void setPingInterval(int pingInterval) { + this.pingInterval = pingInterval; + } + + public int getAutoWhoInterval() { + return autoWhoInterval; + } + + public void setAutoWhoInterval(int autoWhoInterval) { + this.autoWhoInterval = autoWhoInterval; + } + + public int getMaxPingCount() { + return maxPingCount; + } + + public void setMaxPingCount(int maxPingCount) { + this.maxPingCount = maxPingCount; + } + + public boolean isPingTimeoutEnabled() { + return pingTimeoutEnabled; + } + + public void setPingTimeoutEnabled(boolean pingTimeoutEnabled) { + this.pingTimeoutEnabled = pingTimeoutEnabled; + } + + @Override + public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) { + + } + + @Override + public void update(NetworkConfig from) { + this.autoWhoNickLimit = from.autoWhoNickLimit; + this.autoWhoDelay = from.autoWhoDelay; + this.autoWhoEnabled = from.autoWhoEnabled; + this.standardCtcp = from.standardCtcp; + this.pingInterval = from.pingInterval; + this.autoWhoInterval = from.autoWhoInterval; + this.maxPingCount = from.maxPingCount; + this.pingTimeoutEnabled = from.pingTimeoutEnabled; + } + + @Override + public void update(Map<String, QVariant> from) { + update(NetworkConfigSerializer.get().fromDatastream(from)); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java index 299480bc1..1b7830386 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java @@ -4,15 +4,18 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.util.Arrays; +import java.util.Map; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.functions.types.SyncFunction; +import de.kuschku.libquassel.message.Message; +import de.kuschku.libquassel.primitives.types.QVariant; import static de.kuschku.util.AndroidAssert.assertNotNull; -public abstract class SyncableObject { +public abstract class SyncableObject<T extends SyncableObject<T>> { @Nullable protected BusProvider provider; @Nullable @@ -47,4 +50,9 @@ public abstract class SyncableObject { } public abstract void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client); + + public void doInit() {} + + public abstract void update(T from); + public abstract void update(Map<String, QVariant> from); } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppContext.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppContext.java new file mode 100644 index 000000000..2851cbb08 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppContext.java @@ -0,0 +1,65 @@ +package de.kuschku.quasseldroid_ng.ui; + +import de.kuschku.libquassel.BusProvider; +import de.kuschku.libquassel.Client; +import de.kuschku.quasseldroid_ng.ui.chat.WrappedSettings; +import de.kuschku.util.ui.ThemeUtil; + +public class AppContext { + private ThemeUtil themeUtil; + private WrappedSettings settings; + private Client client; + private BusProvider provider; + + public ThemeUtil getThemeUtil() { + return themeUtil; + } + + public void setThemeUtil(ThemeUtil themeUtil) { + this.themeUtil = themeUtil; + } + + public AppContext withThemeUtil(ThemeUtil themeUtil) { + setThemeUtil(themeUtil); + return this; + } + + public WrappedSettings getSettings() { + return settings; + } + + public void setSettings(WrappedSettings settings) { + this.settings = settings; + } + + public AppContext withSettings(WrappedSettings settings) { + setSettings(settings); + return this; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + public AppContext withClient(Client client) { + setClient(client); + return this; + } + + public BusProvider getProvider() { + return provider; + } + + public void setProvider(BusProvider provider) { + this.provider = provider; + } + + public AppContext withProvider(BusProvider provider) { + setProvider(provider); + return this; + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppTheme.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppTheme.java index 12b8d76aa..c24ebeff6 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppTheme.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppTheme.java @@ -29,4 +29,11 @@ public enum AppTheme { case "QUASSEL": return QUASSEL; } } + + @Override + public String toString() { + return name() + "{" + + "themeId=" + themeId + + '}'; + } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigWrapper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigWrapper.java new file mode 100644 index 000000000..474ba962c --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigWrapper.java @@ -0,0 +1,104 @@ +package de.kuschku.quasseldroid_ng.ui.chat; + +import com.mikepenz.materialdrawer.Drawer; +import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; + +import java.lang.reflect.Array; +import java.util.ArrayList; + +import de.kuschku.libquassel.Client; +import de.kuschku.libquassel.syncables.types.BufferViewConfig; +import de.kuschku.libquassel.syncables.types.Network; +import de.kuschku.quasseldroid_ng.ui.AppContext; +import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkItem; +import de.kuschku.util.observables.callbacks.ElementCallback; +import de.kuschku.util.observables.lists.ObservableComparableSortedList; +import de.kuschku.util.observables.lists.ObservableSortedList; + +import static de.kuschku.util.AndroidAssert.assertNotNull; + +public class BufferViewConfigWrapper { + private Drawer drawer; + private BufferViewConfig config; + private ObservableSortedList<NetworkItem> networks = new ObservableSortedList<>(NetworkItem.class, new ObservableSortedList.ItemComparator<NetworkItem>() { + @Override + public int compare(NetworkItem o1, NetworkItem o2) { + return o1.getName().getText().compareTo(o2.getName().getText()); + } + + @Override + public boolean areContentsTheSame(NetworkItem oldItem, NetworkItem newItem) { + return oldItem.getNetwork().getNetworkId() == newItem.getNetwork().getNetworkId(); + } + + @Override + public boolean areItemsTheSame(NetworkItem item1, NetworkItem item2) { + return item1 == item2; + } + }); + + public BufferViewConfigWrapper(AppContext context, BufferViewConfig config, Drawer drawer) { + this.config = config; + this.drawer = drawer; + config.doLateInit(); + networks.clear(); + for (Integer networkId : config.getNetworkList()) { + Network network = context.getClient().getNetwork(networkId); + assertNotNull(network); + networks.add(new NetworkItem(context, network, config)); + } + config.getNetworkList().addCallback(new ElementCallback<Integer>() { + @Override + public void notifyItemInserted(Integer element) { + networks.add(new NetworkItem(context, context.getClient().getNetwork(element), config)); + } + + @Override + public void notifyItemRemoved(Integer element) { + for (NetworkItem network : networks) { + if (network.getNetwork().getNetworkId() == element) { + networks.remove(network); + break; + } + } + } + + @Override + public void notifyItemChanged(Integer element) { + for (NetworkItem network : networks) { + if (network.getNetwork().getNetworkId() == element) { + networks.notifyItemChanged(networks.indexOf(network)); + break; + } + } + } + }); + } + + public void updateDrawerItems() { + drawer.removeAllItems(); + for (IDrawerItem item : getItems()) { + drawer.addItem(item); + } + for (int i = 0; i < drawer.getAdapter().getItemCount(); i++) { + IDrawerItem item = drawer.getAdapter().getItem(i); + if (item instanceof NetworkItem) { + NetworkItem networkItem = (NetworkItem) item; + if (networkItem.getNetwork().isConnected()) + drawer.getAdapter().expand(i); + } + } + } + + public void setDrawer(Drawer drawer) { + this.drawer = drawer; + } + + public ArrayList<IDrawerItem> getItems() { + ArrayList<IDrawerItem> items = new ArrayList<>(); + for (IDrawerItem item : networks) { + items.add(item); + } + return items; + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java index 9e55b5786..f7fb9ed33 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java @@ -1,6 +1,8 @@ package de.kuschku.quasseldroid_ng.ui.chat; import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; @@ -8,66 +10,78 @@ import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; -import android.support.design.widget.NavigationView; -import android.support.v4.widget.DrawerLayout; +import android.support.design.widget.Snackbar; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.AppCompatEditText; import android.support.v7.widget.AppCompatImageButton; -import android.support.v7.widget.AppCompatSpinner; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.RelativeLayout; +import android.util.Log; +import android.view.View; +import com.afollestad.materialdialogs.MaterialDialog; +import com.google.common.base.Splitter; import com.mikepenz.fastadapter.FastAdapter; -import com.mikepenz.materialize.util.UIUtils; +import com.mikepenz.fastadapter.IExpandable; +import com.mikepenz.fastadapter.IItem; +import com.mikepenz.materialdrawer.AccountHeader; +import com.mikepenz.materialdrawer.AccountHeaderBuilder; +import com.mikepenz.materialdrawer.Drawer; +import com.mikepenz.materialdrawer.DrawerBuilder; +import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; +import com.mikepenz.materialdrawer.model.ProfileDrawerItem; +import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; +import com.mikepenz.materialdrawer.model.interfaces.IProfile; import com.sothree.slidinguppanel.SlidingUpPanelLayout; -import aspm.OnChangeListener; -import aspm.PreferenceElement; -import aspm.StringPreference; -import aspm.annotations.Preference; +import java.util.Map; + import aspm.annotations.PreferenceWrapper; import butterknife.Bind; import butterknife.ButterKnife; +import de.kuschku.libquassel.BusProvider; +import de.kuschku.libquassel.Client; +import de.kuschku.libquassel.events.BacklogReceivedEvent; +import de.kuschku.libquassel.events.ConnectionChangeEvent; +import de.kuschku.libquassel.events.GeneralErrorEvent; +import de.kuschku.libquassel.events.LagChangedEvent; +import de.kuschku.libquassel.exceptions.UnknownTypeException; +import de.kuschku.libquassel.localtypes.Buffer; +import de.kuschku.libquassel.message.Message; +import de.kuschku.libquassel.syncables.types.BufferViewConfig; +import de.kuschku.libquassel.syncables.types.BufferViewManager; +import de.kuschku.libquassel.syncables.types.Network; import de.kuschku.quasseldroid_ng.BuildConfig; import de.kuschku.quasseldroid_ng.R; import de.kuschku.quasseldroid_ng.service.ClientBackgroundThread; import de.kuschku.quasseldroid_ng.service.QuasselService; +import de.kuschku.quasseldroid_ng.ui.AppContext; import de.kuschku.quasseldroid_ng.ui.AppTheme; import de.kuschku.quasseldroid_ng.ui.chat.chatview.MessageAdapter; -import de.kuschku.util.DrawerUtils; +import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkItem; +import de.kuschku.util.keyboardutils.DialogKeyboardUtil; +import de.kuschku.util.ServerAddress; import de.kuschku.util.instancestateutil.Storable; import de.kuschku.util.instancestateutil.Store; import de.kuschku.util.observables.AutoScroller; +import de.kuschku.util.observables.lists.ObservableSortedList; +import de.kuschku.util.ui.SpanFormatter; +import de.kuschku.util.ui.ThemeUtil; import static de.kuschku.util.AndroidAssert.assertNotNull; -import static de.kuschku.util.AndroidAssert.assertTrue; @UiThread public class ChatActivity extends AppCompatActivity { - @NonNull - private final Status status = new Status(); - - @Bind(R.id.drawer_left) - DrawerLayout drawerLeft; - - @Bind(R.id.navigation_left) - NavigationView navigationLeft; - @Bind(R.id.toolbar) Toolbar toolbar; - @Bind(R.id.sliding_layout) SlidingUpPanelLayout slidingLayout; @Bind(R.id.chatline) AppCompatEditText chatline; - @Bind(R.id.send) AppCompatImageButton send; @@ -76,40 +90,66 @@ public class ChatActivity extends AppCompatActivity { @Bind(R.id.swipe_view) SwipeRefreshLayout swipeView; - @Bind(R.id.messages) RecyclerView messages; - @Bind(R.id.navigation_header_container) - RelativeLayout navigationHeaderContainer; - @Bind(R.id.buffer_view_spinner) - AppCompatSpinner bufferViewSpinner; - - - WrappedSettings settings; @PreferenceWrapper(BuildConfig.APPLICATION_ID) public static abstract class Settings { String theme; boolean fullHostmask; int textSize; + boolean mircColors; + + String lastHost; + int lastPort; + String lastUsername; + String lastPassword; } - @Nullable - private QuasselService.LocalBinder binder; + private AppContext context = new AppContext(); + + @NonNull + private final Status status = new Status(); + private static class Status extends Storable { + @Store int bufferId = -1; + @Store int bufferViewConfigId = -1; + } - @Nullable + private ServiceInterface serviceInterface = new ServiceInterface(); + private class ServiceInterface { + private void connect(@NonNull ServerAddress address) { + assertNotNull(binder); + disconnect(); + + BusProvider provider = new BusProvider(); + provider.event.register(ChatActivity.this); + binder.startBackgroundThread(provider, address); + onConnectionEstablished(); + } + + private void disconnect() { + if (binder != null) binder.stopBackgroundThread(); + if (backgroundThread != null) backgroundThread.provider.event.unregister(this); + backgroundThread = null; + } + } + + private QuasselService.LocalBinder binder; private ClientBackgroundThread backgroundThread; + private MessageAdapter messageAdapter; + + private AccountHeader accountHeader; + private Drawer drawerLeft; + private BufferViewConfigWrapper wrapper; + private CharSequence subtitle; + private ServiceConnection serviceConnection = new ServiceConnection() { @UiThread public void onServiceConnected(@NonNull ComponentName cn, @NonNull IBinder service) { - assertNotNull(cn); - assertNotNull(service); - if (service instanceof QuasselService.LocalBinder) { ChatActivity.this.binder = (QuasselService.LocalBinder) service; - assertNotNull(binder); if (binder.getBackgroundThread() != null) { connectToThread(binder.getBackgroundThread()); } @@ -118,41 +158,78 @@ public class ChatActivity extends AppCompatActivity { @UiThread public void onServiceDisconnected(@NonNull ComponentName cn) { - assertNotNull(cn); - - backgroundThread = null; + serviceInterface.disconnect(); binder = null; } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { - settings = new WrappedSettings(this); - setTheme(AppTheme.resFromString(settings.theme.get())); + context.setSettings(new WrappedSettings(this)); + AppTheme theme = AppTheme.QUASSEL; + setTheme(theme.themeId); + context.setThemeUtil(new ThemeUtil(this, theme)); super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); + setContentView(R.layout.activity_chat); ButterKnife.bind(this); setSupportActionBar(toolbar); - DrawerUtils.initDrawer(this, drawerLeft, toolbar, R.string.open_drawer, R.string.close_drawer); - - ViewGroup.LayoutParams lp = navigationHeaderContainer.getLayoutParams(); - assertNotNull(lp); - lp.height = lp.height + UIUtils.getStatusBarHeight(this); - navigationHeaderContainer.setLayoutParams(lp); + connectToService(); + + accountHeader = new AccountHeaderBuilder() + .withActivity(this) + .withCompactStyle(true) + .withHeaderBackground(R.drawable.bg) + .withSavedInstance(savedInstanceState) + .withProfileImagesVisible(false) + .withOnAccountHeaderListener((view, profile, current) -> { + if (!current) { + selectBufferViewConfig((int) profile.getIdentifier()); + } + return true; + }) + .build(); + + drawerLeft = new DrawerBuilder() + .withActivity(this) + .withToolbar(toolbar) + .withAccountHeader(accountHeader) + .withSavedInstance(savedInstanceState) + .withTranslucentStatusBar(true) + .build(); + drawerLeft.addStickyFooterItem(new PrimaryDrawerItem().withIcon(R.drawable.ic_server_light).withName("(Re-)Connect").withIdentifier(-1)); + drawerLeft.setOnDrawerItemClickListener((view, position, drawerItem) -> { + long identifier = drawerItem.getIdentifier(); + if (identifier == -1) { + showConnectDialog(); + return false; + } else { + if (((IExpandable) drawerItem).getSubItems() != null) { + drawerLeft.getAdapter().toggleExpandable(position); + return true; + } else { + selectBuffer((int) drawerItem.getIdentifier()); + return false; + } + } + }); - messages.setLayoutManager(new LinearLayoutManager(this)); messages.setItemAnimator(new DefaultItemAnimator()); - messages.setAdapter(new MessageAdapter(this, new AutoScroller(messages))); + messages.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)); + messageAdapter = new MessageAdapter(this, context, new AutoScroller(messages)); + messages.setAdapter(messageAdapter); + msgHistory.setAdapter(new FastAdapter<>()); msgHistory.setLayoutManager(new LinearLayoutManager(this)); msgHistory.setItemAnimator(new DefaultItemAnimator()); - msgHistory.setAdapter(new FastAdapter<>()); - bufferViewSpinner.setAdapter(new ArrayAdapter<>(this, R.layout.md_simplelist_item, android.R.id.title, new String[]{"All Chats", "Queries", "Highlights"})); - bufferViewSpinner.setPopupBackgroundResource(R.drawable.popup_background_material); - bufferViewSpinner.setSelection(0); + swipeView.setOnRefreshListener(() -> { + assertNotNull(context.getClient()); + context.getClient().getBacklogManager().requestMoreBacklog(status.bufferId, 20); + }); + + send.setOnClickListener(view -> sendInput()); } @Override @@ -174,7 +251,7 @@ public class ChatActivity extends AppCompatActivity { private void connectToThread(@NonNull ClientBackgroundThread backgroundThread) { assertNotNull(backgroundThread); - if (this.backgroundThread != null) backgroundThread.provider.event.unregister(this); + serviceInterface.disconnect(); this.backgroundThread = backgroundThread; backgroundThread.provider.event.register(this); @@ -183,35 +260,181 @@ public class ChatActivity extends AppCompatActivity { } private void selectBufferViewConfig(@IntRange(from = -1) int bufferViewConfigId) { + if (wrapper != null) wrapper.setDrawer(null); + drawerLeft.removeAllItems(); if (bufferViewConfigId == -1) { - // TODO: Implement this - + drawerLeft.removeAllItems(); } else { - assertNotNull(this.backgroundThread); - assertNotNull(this.backgroundThread.handler.client.getBufferViewManager()); - assertNotNull(this.backgroundThread.handler.client.getBufferViewManager().BufferViews.get(bufferViewConfigId)); - - // TODO: Implement this + drawerLeft.removeAllItems(); + BufferViewManager bufferViewManager = context.getClient().getBufferViewManager(); + assertNotNull(bufferViewManager); + BufferViewConfig viewConfig = bufferViewManager.BufferViews.get(bufferViewConfigId); + assertNotNull(viewConfig); + + wrapper = new BufferViewConfigWrapper(context, viewConfig, drawerLeft); + wrapper.updateDrawerItems(); } } private void selectBuffer(@IntRange(from = -1) int bufferId) { if (bufferId == -1) { - + messageAdapter.setMessageList(MessageAdapter.emptyList()); + toolbar.setTitle(getResources().getString(R.string.app_name)); } else { - assertNotNull(this.backgroundThread); - assertNotNull(this.backgroundThread.handler.client.getBuffer(bufferId)); + status.bufferId = bufferId; + // Make sure we are actually connected + ObservableSortedList<Message> list = context.getClient().getBacklogManager().getFiltered(status.bufferId); + Buffer buffer = context.getClient().getBuffer(status.bufferId); + // Make sure everything is properly defined + assertNotNull("Buffer is null: " + bufferId, buffer); + assertNotNull(list); + + messageAdapter.setMessageList(list); + toolbar.setTitle(buffer.getName()); + } + } + + private void connectToService() { + Intent intent = new Intent(this, QuasselService.class); + bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + } + + private void onConnectionEstablished() { + assertNotNull(binder); + this.backgroundThread = binder.getBackgroundThread(); + assertNotNull(this.backgroundThread); + context.setClient(this.backgroundThread.handler.client); + assertNotNull(context.getClient()); + } + + private void sendInput() { + if (context.getClient() == null) return; + Buffer buffer = context.getClient().getBuffer(status.bufferId); + assertNotNull(buffer); + CharSequence text = chatline.getText(); + context.getClient().sendInput(buffer.getInfo(), text.toString()); + } + + public void onEventMainThread(ConnectionChangeEvent event) { + setSubtitle(event.status.name()); + + switch (event.status) { + case HANDSHAKE: + break; + case CORE_SETUP_REQUIRED: + break; + case LOGIN_REQUIRED: + assertNotNull(context.getClient()); + + showLoginDialog(); + break; + case USER_SETUP_REQUIRED: + break; + case CONNECTED: + Log.e("TIME", String.valueOf(System.currentTimeMillis())); + updateBufferViewConfigs(); + break; } - assertTrue(bufferId == -1 || null != this.backgroundThread.handler.client.getBuffer(bufferId)); + } - // TODO: Implement this + private void updateBufferViewConfigs() { + Map<Integer, BufferViewConfig> bufferViews = context.getClient().getBufferViewManager().BufferViews; + accountHeader.clear(); + for (Map.Entry<Integer, BufferViewConfig> entry : bufferViews.entrySet()) { + if (entry.getValue() != null) { + accountHeader.addProfiles( + new ProfileDrawerItem() + .withName(entry.getValue().getBufferViewName()) + .withIdentifier(entry.getKey()) + ); + } + } + selectBufferViewConfig(status.bufferViewConfigId); + selectBuffer(status.bufferId); } - private static class Status extends Storable { - @Store - public int bufferId = -1; - @Store - public int bufferViewConfigId = -1; + private void showLoginDialog() { + MaterialDialog dialog = new MaterialDialog.Builder(this) + .title("Address") + .customView(R.layout.dialog_login, false) + .onPositive((dialog1, which) -> { + View parent = dialog1.getCustomView(); + AppCompatEditText usernameField = (AppCompatEditText) parent.findViewById(R.id.username); + AppCompatEditText passwordField = (AppCompatEditText) parent.findViewById(R.id.password); + String username = usernameField.getText().toString(); + String password = passwordField.getText().toString(); + context.getSettings().lastUsername.set(username); + context.getSettings().lastPassword.set(password); + context.getClient().login(username, password); + + Log.e("TIME", String.valueOf(System.currentTimeMillis())); + }) + .positiveText("Login") + .neutralText("Cancel") + .build(); + dialog.setOnKeyListener(new DialogKeyboardUtil(dialog)); + ((AppCompatEditText) dialog.getView().findViewById(R.id.username)).setText(context.getSettings().lastUsername.or("")); + ((AppCompatEditText) dialog.getView().findViewById(R.id.password)).setText(context.getSettings().lastPassword.or("")); + dialog.show(); + } + + public void showConnectDialog() { + MaterialDialog dialog = new MaterialDialog.Builder(this) + .title("Address") + .customView(R.layout.dialog_address, false) + .onPositive((dialog1, which) -> { + View parent = dialog1.getCustomView(); + AppCompatEditText hostField = (AppCompatEditText) parent.findViewById(R.id.host); + AppCompatEditText portField = (AppCompatEditText) parent.findViewById(R.id.port); + String host = hostField.getText().toString().trim(); + int port = Integer.valueOf(portField.getText().toString().trim()); + context.getSettings().lastHost.set(host); + context.getSettings().lastPort.set(port); + serviceInterface.connect(new ServerAddress(host, port)); + }) + .positiveText("Connect") + .neutralText("Cancel") + .build(); + AppCompatEditText hostField = (AppCompatEditText) dialog.getView().findViewById(R.id.host); + AppCompatEditText portField = (AppCompatEditText) dialog.getView().findViewById(R.id.port); + + dialog.setOnKeyListener(new DialogKeyboardUtil(dialog)); + hostField.setText(context.getSettings().lastHost.or("")); + portField.setText(String.valueOf(context.getSettings().lastPort.or(4242))); + + dialog.show(); + } + + public void onEventMainThread(BacklogReceivedEvent event) { + if (event.bufferId == status.bufferId) { + swipeView.setRefreshing(false); + } + } + + public void onEventMainThread(GeneralErrorEvent event) { + Snackbar.make(messages, event.toString(), Snackbar.LENGTH_LONG).show(); + for (String line : Splitter.fixedLength(2048).split(event.toString())) { + Log.e("ChatActivity", line); + } + if (event.exception != null) + event.exception.printStackTrace(); + } + + public void onEventMainThread(LagChangedEvent event) { + updateSubTitle(); + } + + protected void setSubtitle(CharSequence subtitle) { + this.subtitle = subtitle; + updateSubTitle(); + } + + private void updateSubTitle() { + if (context.getClient() != null) { + toolbar.setSubtitle(SpanFormatter.format("Lag: %.2f, %s", context.getClient().getLag() / 1000.0F, subtitle)); + } else { + toolbar.setSubtitle(subtitle); + } } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java index 28b3d4c71..50205398e 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java @@ -16,6 +16,7 @@ import de.kuschku.libquassel.localtypes.Buffer; import de.kuschku.libquassel.message.Message; import de.kuschku.quasseldroid_ng.BuildConfig; import de.kuschku.quasseldroid_ng.R; +import de.kuschku.quasseldroid_ng.ui.AppContext; import de.kuschku.quasseldroid_ng.ui.AppTheme; import de.kuschku.util.annotationbind.AutoBinder; import de.kuschku.util.annotationbind.AutoString; @@ -40,26 +41,14 @@ public class ChatMessageRenderer { private MessageStyleContainer actionStyle; private MessageStyleContainer plainStyle; - @Nullable - private Client client; - - //@NonNull - //private final SharedPreferences preferences; - - public ChatMessageRenderer(@NonNull Context ctx) { - this(ctx, new ThemeUtil(ctx)); - } - - public ChatMessageRenderer(@NonNull Context ctx, @NonNull AppTheme theme) { - this(ctx, new ThemeUtil(ctx, theme)); - } - - public ChatMessageRenderer(@NonNull Context ctx, @NonNull ThemeUtil themeUtil) { - //this.preferences = ctx.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE); + @NonNull + private AppContext context; + public ChatMessageRenderer(@NonNull Context ctx, @NonNull AppContext context) { this.format = DateFormatHelper.getTimeFormatter(ctx); this.strings = new FormatStrings(ctx); - setTheme(themeUtil); + this.context = context; + setTheme(context.getThemeUtil()); } public void setTheme(ThemeUtil themeUtil) { @@ -91,10 +80,6 @@ public class ChatMessageRenderer { ); } - public void setClient(@NonNull Client client) { - this.client = client; - } - private void applyStyle(@NonNull MessageViewHolder holder, @NonNull MessageStyleContainer style, @NonNull MessageStyleContainer highlightStyle, boolean highlight) { MessageStyleContainer container = highlight ? highlightStyle : style; holder.content.setTextColor(container.textColor); @@ -115,13 +100,13 @@ public class ChatMessageRenderer { @NonNull private CharSequence formatNick(@NonNull String hostmask) { - return formatNick(hostmask, true); + return formatNick(hostmask, context.getSettings().fullHostmask.or(false)); } @NonNull private CharSequence getBufferName(Message message) { - assertNotNull(client); - Buffer buffer = client.getBuffer(message.bufferInfo.id); + assertNotNull(context.getClient()); + Buffer buffer = context.getClient().getBuffer(message.bufferInfo.id); assertNotNull(buffer); String name = buffer.getName(); assertNotNull(name); diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java index a2d0d3f5f..61e434651 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java @@ -11,6 +11,7 @@ import android.view.ViewGroup; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.message.Message; import de.kuschku.quasseldroid_ng.R; +import de.kuschku.quasseldroid_ng.ui.AppContext; import de.kuschku.util.observables.AutoScroller; import de.kuschku.util.observables.callbacks.UICallback; import de.kuschku.util.observables.callbacks.wrappers.AdapterUICallbackWrapper; @@ -29,18 +30,14 @@ public class MessageAdapter extends RecyclerView.Adapter<MessageViewHolder> { @NonNull private final UICallback callback; @NonNull - private IObservableList<UICallback, Message> messageList = new ObservableComparableSortedList<>(Message.class); + private IObservableList<UICallback, Message> messageList = emptyList(); - public MessageAdapter(@NonNull Context ctx, @Nullable AutoScroller scroller) { + public MessageAdapter(@NonNull Context ctx, @NonNull AppContext context, @Nullable AutoScroller scroller) { this.inflater = LayoutInflater.from(ctx); - this.renderer = new ChatMessageRenderer(ctx); + this.renderer = new ChatMessageRenderer(ctx, context); this.callback = new AdapterUICallbackWrapper(this, scroller); } - public void setClient(@NonNull Client client) { - renderer.setClient(client); - } - public void setMessageList(@NonNull ObservableSortedList<Message> messageList) { this.messageList.removeCallback(callback); this.messageList = messageList; @@ -66,4 +63,9 @@ public class MessageAdapter extends RecyclerView.Adapter<MessageViewHolder> { public int getItemCount() { return messageList.size(); } + + private static ObservableSortedList<Message> emptyList = new ObservableComparableSortedList<Message>(Message.class); + public static ObservableSortedList<Message> emptyList() { + return emptyList; + } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java new file mode 100644 index 000000000..320a41bb2 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java @@ -0,0 +1,144 @@ +package de.kuschku.quasseldroid_ng.ui.chat.drawer; + +import android.graphics.Color; +import android.view.View; +import android.widget.TextView; + +import com.mikepenz.materialdrawer.holder.BadgeStyle; +import com.mikepenz.materialdrawer.holder.ColorHolder; +import com.mikepenz.materialdrawer.holder.ImageHolder; +import com.mikepenz.materialdrawer.holder.StringHolder; +import com.mikepenz.materialdrawer.model.SecondaryDrawerItem; +import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; + +import de.kuschku.libquassel.localtypes.Buffer; +import de.kuschku.libquassel.localtypes.ChannelBuffer; +import de.kuschku.libquassel.localtypes.QueryBuffer; +import de.kuschku.libquassel.localtypes.StatusBuffer; +import de.kuschku.libquassel.message.Message; +import de.kuschku.quasseldroid_ng.R; +import de.kuschku.quasseldroid_ng.ui.AppContext; +import de.kuschku.util.observables.IObservable; +import de.kuschku.util.observables.callbacks.GeneralCallback; +import de.kuschku.util.observables.callbacks.wrappers.GeneralCallbackWrapper; +import de.kuschku.util.observables.callbacks.wrappers.GeneralUICallbackWrapper; +import de.kuschku.util.observables.lists.ObservableComparableSortedList; +import de.kuschku.util.ui.MessageUtil; + +public class BufferItem extends SecondaryDrawerItem implements IObservable<GeneralCallback>, GeneralCallback { + private final Buffer buffer; + private final AppContext context; + private final ObservableComparableSortedList<Message> notifications; + + private GeneralCallbackWrapper callback = new GeneralCallbackWrapper(); + + public BufferItem(Buffer buffer, AppContext context) { + this.buffer = buffer; + this.context = context; + notifications = context.getClient().getNotificationManager().getNotifications(buffer.getInfo().id); + notifications.addCallback(new GeneralUICallbackWrapper() { + @Override + public void notifyChanged() { + BufferItem.this.notifyChanged(); + } + }); + } + + public void notifyChanged() { + callback.notifyChanged(); + } + + @Override + public StringHolder getBadge() { + return new StringHolder(String.valueOf(notifications.size())); + } + + @Override + public BadgeStyle getBadgeStyle() { + if (notifications.isEmpty()) { + return new BadgeStyle(); + } else { + return new BadgeStyle().withTextColor(Color.WHITE).withColorRes(R.color.md_red_700); + } + } + + @Override + public StringHolder getDescription() { + if (buffer instanceof QueryBuffer) { + QueryBuffer queryBuffer = (QueryBuffer) buffer; + if (queryBuffer.getUser() != null) + return new StringHolder(queryBuffer.getUser().getRealName()); + } else if (buffer instanceof StatusBuffer) { + + } else if (buffer instanceof ChannelBuffer) { + ChannelBuffer channelBuffer = (ChannelBuffer) buffer; + if (channelBuffer.getChannel() != null) + return new StringHolder(channelBuffer.getChannel().getTopic()); + } + return super.getDescription(); + } + + @Override + public StringHolder getName() { + return new StringHolder(buffer.getName()); + } + + @Override + public ImageHolder getIcon() { + if (buffer instanceof ChannelBuffer) { + if (buffer.isActive()) { + return new ImageHolder(R.drawable.ic_status_channel); + } else { + return new ImageHolder(R.drawable.ic_status_channel_offline); + } + } else if (buffer instanceof StatusBuffer) { + if (buffer.isActive()) { + return new ImageHolder(R.drawable.ic_status); + } else { + return new ImageHolder(R.drawable.ic_status_offline); + } + } else { + if (buffer.isActive()) { + return new ImageHolder(R.drawable.ic_status); + } else { + return new ImageHolder(R.drawable.ic_status_offline); + } + } + } + + @Override + public ColorHolder getIconColor() { + return super.getIconColor(); + } + + @Override + public void addCallback(GeneralCallback callback) { + this.callback.addCallback(callback); + } + + @Override + public void removeCallback(GeneralCallback callback) { + this.callback.removeCallback(callback); + } + + public Buffer getBuffer() { + return buffer; + } + + @Override + public long getIdentifier() { + return buffer.getInfo().id; + } + + @Override + public void onPostBindView(IDrawerItem drawerItem, View view) { + super.onPostBindView(drawerItem, view); + + if (getDescription() != null && getDescription().getText() != null) + ((TextView) view.findViewById(R.id.material_drawer_description)).setText(MessageUtil.parseStyleCodes( + context.getThemeUtil(), + getDescription().getText(), + context.getSettings().mircColors.or(true) + )); + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferWrapper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferWrapper.java deleted file mode 100644 index 5b435df17..000000000 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferWrapper.java +++ /dev/null @@ -1,78 +0,0 @@ -package de.kuschku.quasseldroid_ng.ui.chat.drawer; - -import android.support.annotation.NonNull; -import android.support.annotation.UiThread; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bignerdranch.expandablerecyclerview.ViewHolder.ChildViewHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.model.interfaces.Nameable; - -import butterknife.Bind; -import butterknife.ButterKnife; -import de.kuschku.libquassel.localtypes.Buffer; -import de.kuschku.quasseldroid_ng.R; -import de.kuschku.util.ui.Bindable; - -@UiThread -public class BufferWrapper implements Nameable<BufferWrapper> { - @NonNull - private final Buffer buffer; - - public BufferWrapper(@NonNull Buffer buffer) { - this.buffer = buffer; - } - - @NonNull - @Override - public BufferWrapper withName(String name) { - return this; - } - - @NonNull - @Override - public BufferWrapper withName(int nameRes) { - return this; - } - - @NonNull - @Override - public BufferWrapper withName(StringHolder name) { - return this; - } - - @NonNull - @Override - public StringHolder getName() { - return new StringHolder(buffer.getName()); - } - - @NonNull - public Buffer getBuffer() { - return buffer; - } - - public static class ViewHolder extends ChildViewHolder implements Bindable<BufferWrapper> { - @Bind(R.id.material_drawer_icon) - ImageView materialDrawerIcon; - @Bind(R.id.material_drawer_name) - TextView materialDrawerName; - @Bind(R.id.material_drawer_description) - TextView materialDrawerDescription; - @Bind(R.id.material_drawer_badge_container) - View materialDrawerBadgeContainer; - @Bind(R.id.material_drawer_badge) - TextView materialDrawerBadge; - - public ViewHolder(@NonNull View itemView) { - super(itemView); - ButterKnife.bind(this, itemView); - } - - public void bind(@NonNull BufferWrapper wrapper) { - materialDrawerName.setText(wrapper.getName().getText()); - } - } -} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java new file mode 100644 index 000000000..7c03161fb --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java @@ -0,0 +1,173 @@ +package de.kuschku.quasseldroid_ng.ui.chat.drawer; + +import android.content.Context; +import android.util.Log; +import android.util.SparseArray; +import android.view.View; +import android.widget.TextView; + +import com.mikepenz.fastadapter.utils.ViewHolderFactory; +import com.mikepenz.materialdrawer.holder.ColorHolder; +import com.mikepenz.materialdrawer.holder.StringHolder; +import com.mikepenz.materialdrawer.model.BaseViewHolder; +import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; +import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; + +import java.util.ArrayList; +import java.util.List; + +import de.kuschku.libquassel.Client; +import de.kuschku.libquassel.localtypes.Buffer; +import de.kuschku.libquassel.syncables.types.BufferViewConfig; +import de.kuschku.libquassel.syncables.types.Network; +import de.kuschku.quasseldroid_ng.ui.AppContext; +import de.kuschku.util.AndroidAssert; +import de.kuschku.util.observables.IObservable; +import de.kuschku.util.observables.callbacks.ElementCallback; +import de.kuschku.util.observables.callbacks.GeneralCallback; +import de.kuschku.util.observables.callbacks.wrappers.GeneralCallbackWrapper; +import de.kuschku.util.observables.lists.ObservableSortedList; + +import static de.kuschku.util.AndroidAssert.*; + +public class NetworkItem extends PrimaryDrawerItem implements IObservable<GeneralCallback>, GeneralCallback { + private final AppContext context; + private final Network network; + private final BufferViewConfig config; + private final ObservableSortedList<BufferItem> buffers = new ObservableSortedList<>(BufferItem.class, new AlphabeticalComparator()); + private final SparseArray<BufferItem> bufferIds = new SparseArray<>(); + + private final GeneralCallbackWrapper callback = new GeneralCallbackWrapper(); + + public NetworkItem(AppContext context, Network network, BufferViewConfig config) { + this.context = context; + this.network = network; + this.config = config; + + for (Integer bufferId : this.config.getBufferList()) { + Buffer buffer = context.getClient().getBuffer(bufferId); + if (buffer != null && buffer.getInfo().networkId == network.getNetworkId()) { + this.buffers.add(new BufferItem(buffer, context)); + Log.e("Drawer", "Buffer can not be null! BufferId: "+ bufferId); + } + } + this.config.getBufferList().addCallback(new ElementCallback<Integer>() { + @Override + public void notifyItemInserted(Integer element) { + if (network.getBuffers().contains(element)) { + if (bufferIds.get(element) == null) { + Buffer buffer = context.getClient().getBuffer(element); + + BufferItem bufferItem = new BufferItem(buffer, context); + buffers.add(bufferItem); + bufferItem.addCallback(NetworkItem.this); + bufferIds.put(element, bufferItem); + notifyChanged(); + } + } + } + + @Override + public void notifyItemRemoved(Integer element) { + if (bufferIds.get(element) != null) { + bufferIds.remove(element); + notifyChanged(); + } + } + + @Override + public void notifyItemChanged(Integer element) { + if (bufferIds.get(element) != null) { + notifyChanged(); + } + } + }); + } + + @Override + public boolean isIconTinted() { + return super.isIconTinted(); + } + + @Override + public ColorHolder getIconColor() { + return super.getIconColor(); + } + + @Override + public StringHolder getDescription() { + return new StringHolder(String.valueOf(network.getLatency())); + } + + @Override + public StringHolder getName() { + return new StringHolder(network.getNetworkName()); + } + + @Override + public List<IDrawerItem> getSubItems() { + ArrayList<IDrawerItem> items = new ArrayList<>(); + for (IDrawerItem item : buffers) { + items.add(item); + } + return items; + } + + @Override + public void notifyChanged() { + this.callback.notifyChanged(); + } + + @Override + public void addCallback(GeneralCallback callback) { + this.callback.addCallback(callback); + } + + @Override + public void removeCallback(GeneralCallback callback) { + this.callback.removeCallback(callback); + } + + public Network getNetwork() { + return network; + } + + @Override + public long getIdentifier() { + return network.getNetworkId(); + } + + class AlphabeticalComparator implements ObservableSortedList.ItemComparator<BufferItem> { + @Override + public int compare(BufferItem o1, BufferItem o2) { + return o1.getName().getText().compareTo(o2.getName().getText()); + } + + @Override + public boolean areContentsTheSame(BufferItem oldItem, BufferItem newItem) { + return oldItem.getBuffer().getInfo().id == newItem.getBuffer().getInfo().id; + } + + @Override + public boolean areItemsTheSame(BufferItem item1, BufferItem item2) { + return item1 == item2; + } + } + + class NoneComparator implements ObservableSortedList.ItemComparator<BufferItem> { + @Override + public int compare(BufferItem o1, BufferItem o2) { + return o1.getBuffer().getInfo().id - o2.getBuffer().getInfo().id; + } + + @Override + public boolean areContentsTheSame(BufferItem oldItem, BufferItem newItem) { + return oldItem.getBuffer().getInfo().id == newItem.getBuffer().getInfo().id; + } + + @Override + public boolean areItemsTheSame(BufferItem item1, BufferItem item2) { + return item1 == item2; + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkWrapper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkWrapper.java deleted file mode 100644 index 3a9beab7b..000000000 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkWrapper.java +++ /dev/null @@ -1,275 +0,0 @@ -package de.kuschku.quasseldroid_ng.ui.chat.drawer; - -import android.support.annotation.NonNull; -import android.support.annotation.UiThread; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bignerdranch.expandablerecyclerview.Model.ParentListItem; -import com.bignerdranch.expandablerecyclerview.ViewHolder.ParentViewHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.model.interfaces.Nameable; - -import java.util.List; - -import butterknife.Bind; -import butterknife.ButterKnife; -import de.kuschku.libquassel.Client; -import de.kuschku.libquassel.localtypes.Buffer; -import de.kuschku.libquassel.message.Message; -import de.kuschku.libquassel.syncables.types.BufferViewConfig; -import de.kuschku.libquassel.syncables.types.Network; -import de.kuschku.quasseldroid_ng.R; -import de.kuschku.util.observables.ContentComparable; -import de.kuschku.util.observables.callbacks.ElementCallback; -import de.kuschku.util.observables.callbacks.UIChildCallback; -import de.kuschku.util.observables.callbacks.wrappers.ChildUICallbackWrapper; -import de.kuschku.util.observables.callbacks.wrappers.MultiUIChildCallback; -import de.kuschku.util.observables.lists.ObservableSortedList; -import de.kuschku.util.ui.Bindable; - -@UiThread -public class NetworkWrapper implements ParentListItem, Nameable<NetworkWrapper>, ContentComparable<NetworkWrapper> { - @NonNull - private final MultiUIChildCallback callback = MultiUIChildCallback.of(); - - @NonNull - private final Network network; - @NonNull - private final Client client; - @NonNull - private final ChildUICallbackWrapper wrapper = new ChildUICallbackWrapper(callback); - @NonNull - private ObservableSortedList<BufferWrapper> buffers = new ObservableSortedList<>(BufferWrapper.class, new BufferWrapperSorterWrapper(getSorter(SortMode.NONE))); - private int groupId; - - public NetworkWrapper(@NonNull Network network, @NonNull Client client, @NonNull BufferViewConfig bufferViewConfig, @NonNull SortMode sortMode, int groupId) { - this.network = network; - this.client = client; - setSortMode(sortMode); - bufferViewConfig.getBufferList().addCallback(new ElementCallback<Integer>() { - @Override - public void notifyItemInserted(Integer element) { - Buffer buffer = client.getBuffer(element); - if (buffer.getInfo().networkId != network.getNetworkId()) return; - - buffers.add(new BufferWrapper(buffer)); - } - - @Override - public void notifyItemRemoved(Integer element) { - Buffer buffer = client.getBuffer(element); - if (buffer.getInfo().networkId != network.getNetworkId()) return; - - buffers.remove(new BufferWrapper(buffer)); - } - - @Override - public void notifyItemChanged(Integer element) { - Buffer buffer = client.getBuffer(element); - if (buffer.getInfo().networkId != network.getNetworkId()) return; - - callback.notifyChildItemChanged(groupId, buffers.indexOf(new BufferWrapper(buffer))); - } - }); - buffers.addCallback(wrapper); - wrapper.setGroupPosition(groupId); - } - - public void addCallback(@NonNull UIChildCallback callback) { - this.callback.addCallback(callback); - } - - public void removeCallback(@NonNull UIChildCallback callback) { - this.callback.removeCallback(callback); - } - - public void setGroupId(int groupId) { - this.groupId = groupId; - this.wrapper.setGroupPosition(groupId); - } - - public void setSortMode(@NonNull SortMode sortMode) { - ObservableSortedList.ItemComparator<BufferWrapper> sorter = new BufferWrapperSorterWrapper(getSorter(sortMode)); - ObservableSortedList<BufferWrapper> newBuffers = new ObservableSortedList<>(BufferWrapper.class, sorter); - if (buffers.size() > newBuffers.size()) { - for (int i = newBuffers.size(); i < buffers.size(); i++) { - callback.notifyChildItemRemoved(groupId, i); - } - } else if (newBuffers.size() > buffers.size()) { - for (int i = buffers.size(); i < newBuffers.size(); i++) { - callback.notifyChildItemInserted(groupId, i); - } - } - - int commonElementCount = Math.min(buffers.size(), newBuffers.size()); - for (int i = 0; i < commonElementCount; i++) { - callback.notifyChildItemChanged(groupId, i); - } - buffers = newBuffers; - } - - @NonNull - private ObservableSortedList.ItemComparator<Buffer> getSorter(@NonNull SortMode sortMode) { - switch (sortMode) { - case ALPHABETICAL: - return new AlphabeticalBufferSorter(); - case RECENT_ACTIVITY: - return new RecentActivityBufferSorter(); - case LAST_SEEN: - return new LastSeenBufferSorter(); - default: - return new IdBufferSorter(); - } - } - - @NonNull - @Override - public List<?> getChildItemList() { - return buffers; - } - - @Override - public boolean isInitiallyExpanded() { - return network.isConnected(); - } - - @NonNull - @Override - public NetworkWrapper withName(String name) { - return this; - } - - @NonNull - @Override - public NetworkWrapper withName(int nameRes) { - return this; - } - - @NonNull - @Override - public NetworkWrapper withName(StringHolder name) { - return this; - } - - @NonNull - @Override - public StringHolder getName() { - return new StringHolder(network.getNetworkName()); - } - - @Override - public boolean equalsContent(@NonNull NetworkWrapper other) { - return network.equalsContent(other.network); - } - - @Override - public int compareTo(@NonNull NetworkWrapper another) { - return network.compareTo(another.network); - } - - public enum SortMode { - NONE, - ALPHABETICAL, - RECENT_ACTIVITY, - LAST_SEEN - } - - public static class ViewHolder extends ParentViewHolder implements Bindable<NetworkWrapper> { - @Bind(R.id.material_drawer_icon) - ImageView materialDrawerIcon; - @Bind(R.id.material_drawer_name) - TextView materialDrawerName; - @Bind(R.id.material_drawer_description) - TextView materialDrawerDescription; - @Bind(R.id.material_drawer_badge_container) - View materialDrawerBadgeContainer; - @Bind(R.id.material_drawer_badge) - TextView materialDrawerBadge; - - public ViewHolder(@NonNull View itemView) { - super(itemView); - ButterKnife.bind(this, itemView); - } - - public void bind(@NonNull NetworkWrapper wrapper) { - materialDrawerName.setText(wrapper.getName().getText()); - } - } - - private static class BufferWrapperSorterWrapper implements ObservableSortedList.ItemComparator<BufferWrapper> { - @NonNull - private final ObservableSortedList.ItemComparator<Buffer> wrapped; - - public BufferWrapperSorterWrapper(@NonNull ObservableSortedList.ItemComparator<Buffer> wrapped) { - this.wrapped = wrapped; - } - - @Override - public int compare(@NonNull BufferWrapper lhs, @NonNull BufferWrapper rhs) { - return wrapped.compare(lhs.getBuffer(), rhs.getBuffer()); - } - - @Override - public boolean areContentsTheSame(@NonNull BufferWrapper oldItem, @NonNull BufferWrapper newItem) { - return wrapped.areContentsTheSame(oldItem.getBuffer(), newItem.getBuffer()); - } - - @Override - public boolean areItemsTheSame(@NonNull BufferWrapper item1, @NonNull BufferWrapper item2) { - return areContentsTheSame(item1, item2); - } - } - - private class IdBufferSorter extends BasicBufferSorter { - @Override - public int compare(@NonNull Buffer lhs, @NonNull Buffer rhs) { - return lhs.getInfo().id - rhs.getInfo().id; - } - } - - private class AlphabeticalBufferSorter extends BasicBufferSorter { - @Override - public int compare(@NonNull Buffer lhs, @NonNull Buffer rhs) { - return lhs.getName().compareTo(rhs.getName()); - } - } - - private class RecentActivityBufferSorter extends BasicBufferSorter { - @Override - public int compare(@NonNull Buffer lhs, @NonNull Buffer rhs) { - return getLastMessageId(lhs) - getLastMessageId(rhs); - } - - private int getLastMessageId(@NonNull Buffer buffer) { - int bufferId = buffer.getInfo().id; - Message message = client.getBacklogManager().get(bufferId).last(); - return message == null ? -1 : message.messageId; - } - } - - private class LastSeenBufferSorter extends BasicBufferSorter { - @Override - public int compare(@NonNull Buffer lhs, @NonNull Buffer rhs) { - return getLastSeenMessageId(lhs) - getLastSeenMessageId(rhs); - } - - private int getLastSeenMessageId(@NonNull Buffer buffer) { - int bufferId = buffer.getInfo().id; - return client.getBufferSyncer().getLastSeenMsg(bufferId); - } - } - - private abstract class BasicBufferSorter implements ObservableSortedList.ItemComparator<Buffer> { - @Override - public boolean areContentsTheSame(@NonNull Buffer oldItem, @NonNull Buffer newItem) { - return oldItem.getInfo().id == newItem.getInfo().id; - } - - @Override - public boolean areItemsTheSame(@NonNull Buffer item1, @NonNull Buffer item2) { - return areContentsTheSame(item1, item2); - } - } -} diff --git a/app/src/main/java/de/kuschku/util/AndroidAssert.java b/app/src/main/java/de/kuschku/util/AndroidAssert.java index 69937703a..f9d7bb0f8 100644 --- a/app/src/main/java/de/kuschku/util/AndroidAssert.java +++ b/app/src/main/java/de/kuschku/util/AndroidAssert.java @@ -1,10 +1,11 @@ package de.kuschku.util; import android.support.annotation.Nullable; -import android.support.design.BuildConfig; import junit.framework.Assert; +import de.kuschku.quasseldroid_ng.BuildConfig; + /** * Class to provide the Assert functionality of JUnit at runtime for debug builds */ @@ -13,6 +14,12 @@ public class AndroidAssert extends Assert { private AndroidAssert() { } + public static void assertTrue(Throwable message, boolean condition) { + if (BuildConfig.DEBUG) { + if (!condition) fail(message); + } + } + public static void assertTrue(String message, boolean condition) { if (BuildConfig.DEBUG) { if (!condition) fail(message); @@ -25,13 +32,9 @@ public class AndroidAssert extends Assert { } } - public static void assertTrueOrNotNull(boolean condition, @Nullable Object... nonNull) { + public static void assertFalse(Throwable message, boolean condition) { if (BuildConfig.DEBUG) { - if (condition) return; - if (nonNull == null) fail(); - for (Object o : nonNull) { - if (o == null) fail(); - } + if (condition) fail(message); } } @@ -47,6 +50,12 @@ public class AndroidAssert extends Assert { } } + public static void fail(Throwable message) { + if (BuildConfig.DEBUG) { + throw new RuntimeException(message); + } + } + public static void fail(String message) { if (BuildConfig.DEBUG) { throw new AssertionError(message); @@ -59,6 +68,12 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, Object expected, Object actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + public static void assertEquals(String message, Object expected, Object actual) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -71,6 +86,12 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, String expected, String actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + public static void assertEquals(String message, String expected, String actual) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -83,6 +104,13 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, double expected, double actual, double delta) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + + public static void assertEquals(String message, double expected, double actual, double delta) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -95,6 +123,12 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, float expected, float actual, float delta) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + public static void assertEquals(String message, float expected, float actual, float delta) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -107,6 +141,12 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, long expected, long actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + public static void assertEquals(String message, long expected, long actual) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -119,6 +159,12 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, boolean expected, boolean actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + public static void assertEquals(String message, boolean expected, boolean actual) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -131,6 +177,12 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, byte expected, byte actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + public static void assertEquals(String message, byte expected, byte actual) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -143,6 +195,12 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, char expected, char actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + public static void assertEquals(String message, char expected, char actual) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -155,6 +213,12 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, short expected, short actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + public static void assertEquals(String message, short expected, short actual) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -167,6 +231,12 @@ public class AndroidAssert extends Assert { } } + public static void assertEquals(Throwable message, int expected, int actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, Objects.equals(expected, actual)); + } + } + public static void assertEquals(String message, int expected, int actual) { if (BuildConfig.DEBUG) { assertTrue(message, Objects.equals(expected, actual)); @@ -191,6 +261,12 @@ public class AndroidAssert extends Assert { } } + public static void assertNotNull(Throwable message, @Nullable Object object) { + if (BuildConfig.DEBUG) { + assertTrue(message, object != null); + } + } + public static void assertNull(@Nullable Object object) { if (BuildConfig.DEBUG) { assertTrue(object == null); @@ -203,18 +279,37 @@ public class AndroidAssert extends Assert { } } + public static void assertNull(Throwable message, @Nullable Object object) { + if (BuildConfig.DEBUG) { + assertTrue(message, object == null); + } + } + + public static void assertSame(String message, Object expected, Object actual) { if (BuildConfig.DEBUG) { assertTrue(message, expected == actual); } } + public static void assertSame(Throwable message, Object expected, Object actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, expected == actual); + } + } + public static void assertSame(Object expected, Object actual) { if (BuildConfig.DEBUG) { assertTrue(expected == actual); } } + public static void assertNotSame(Throwable message, Object expected, Object actual) { + if (BuildConfig.DEBUG) { + assertTrue(message, expected != actual); + } + } + public static void assertNotSame(String message, Object expected, Object actual) { if (BuildConfig.DEBUG) { assertTrue(message, expected != actual); @@ -244,4 +339,16 @@ public class AndroidAssert extends Assert { assertEquals(message, expected, actual); } } + + public static void assertEquals(int... elements) { + if (BuildConfig.DEBUG) { + if (elements.length > 0) { + int first = elements[0]; + for (int i = 1; i < elements.length; i++) { + if (first != elements[i]) + fail(); + } + } + } + } } diff --git a/app/src/main/java/de/kuschku/util/DrawerUtils.java b/app/src/main/java/de/kuschku/util/DrawerUtils.java deleted file mode 100644 index efed3bd9e..000000000 --- a/app/src/main/java/de/kuschku/util/DrawerUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.kuschku.util; - -import android.app.Activity; -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; -import android.support.annotation.UiThread; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.widget.Toolbar; - -import de.kuschku.quasseldroid_ng.R; -import de.kuschku.util.ui.MaterialActionBarDrawerToggle; - -public class DrawerUtils { - @UiThread - public static void initDrawer(@NonNull Activity actvity, @NonNull DrawerLayout layout, Toolbar toolbar, @StringRes int open_res, @StringRes int close_res) { - ActionBarDrawerToggle actionBarDrawerToggle = new MaterialActionBarDrawerToggle(actvity, layout, toolbar, open_res, close_res); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - actvity.getWindow().setStatusBarColor(actvity.getResources().getColor(android.R.color.transparent)); - layout.setStatusBarBackground(R.color.colorPrimaryDark); - } - layout.setDrawerListener(actionBarDrawerToggle); - actionBarDrawerToggle.syncState(); - } -} diff --git a/app/src/main/java/de/kuschku/util/ReflectionUtils.java b/app/src/main/java/de/kuschku/util/ReflectionUtils.java index ff536e629..9dc4ed733 100644 --- a/app/src/main/java/de/kuschku/util/ReflectionUtils.java +++ b/app/src/main/java/de/kuschku/util/ReflectionUtils.java @@ -2,6 +2,7 @@ package de.kuschku.util; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.util.Log; import com.google.common.primitives.Primitives; @@ -32,7 +33,8 @@ public class ReflectionUtils { Class<?>[] classes = new Class<?>[argv.length]; for (int i = 0; i < argv.length; i++) { - classes[i] = argv[i].getClass(); + if (argv[i] == null) classes[i] = null; + else classes[i] = argv[i].getClass(); } Method m = getMethodFromSignature(name, o.getClass(), classes); if (m == null) @@ -41,6 +43,7 @@ public class ReflectionUtils { try { m.invoke(o, argv); } catch (Exception e) { + Log.e("DEBUG", m.toString()); throw new SyncInvocationException(e, String.format("Error invoking %s::%s with arguments %s and classes %s", o.getClass().getSimpleName(), name, Arrays.toString(argv), Arrays.toString(classes))); } } @@ -68,6 +71,10 @@ public class ReflectionUtils { for (int i = 0; i < parameterTypes.length; i++) { Class<?> mParam = m.getParameterTypes()[i]; Class<?> vParam = parameterTypes[i]; + + // Can’t check type of null values, so we’ll assume it will work + if (vParam == null) continue; + assertNotNull(vParam); if (mParam.isPrimitive() && Primitives.isWrapperType(vParam)) diff --git a/app/src/main/java/de/kuschku/util/keyboardutils/DialogKeyboardUtil.java b/app/src/main/java/de/kuschku/util/keyboardutils/DialogKeyboardUtil.java new file mode 100644 index 000000000..3e36cc221 --- /dev/null +++ b/app/src/main/java/de/kuschku/util/keyboardutils/DialogKeyboardUtil.java @@ -0,0 +1,32 @@ +package de.kuschku.util.keyboardutils; + +import android.content.DialogInterface; +import android.view.KeyEvent; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; + +/** + * A util class that automatically handles <code>enter</code> and <code>esc</code> in dialogs + * properly: By calling onPositive or onNeutral + */ +public class DialogKeyboardUtil implements DialogInterface.OnKeyListener { + MaterialDialog dialog; + + public DialogKeyboardUtil(MaterialDialog dialog) { + this.dialog = dialog; + } + + @Override + public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_ENTER) { + dialog.getActionButton(DialogAction.POSITIVE).callOnClick(); + dialog.dismiss(); + return true; + } else if (keyCode == KeyEvent.KEYCODE_ESCAPE) { + dialog.dismiss(); + return true; + } + return false; + } +} diff --git a/app/src/main/java/de/kuschku/util/keyboardutils/EditTextKeyboardUtil.java b/app/src/main/java/de/kuschku/util/keyboardutils/EditTextKeyboardUtil.java new file mode 100644 index 000000000..317a416c5 --- /dev/null +++ b/app/src/main/java/de/kuschku/util/keyboardutils/EditTextKeyboardUtil.java @@ -0,0 +1,62 @@ +package de.kuschku.util.keyboardutils; + +import android.support.v7.widget.AppCompatEditText; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; + +//TODO: FIND A WAY TO TEST THIS – THE EMULATOR DOESN'T WORK +public class EditTextKeyboardUtil implements AppCompatEditText.OnKeyListener { + AppCompatEditText editText; + boolean reverse; + + public EditTextKeyboardUtil(AppCompatEditText editText) { + this.editText = editText; + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + Log.e("DEBUG", keyCode + " : " + event.toString()); + + if (event.isShiftPressed()) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (editText.getSelectionStart() == editText.getSelectionEnd()) + reverse = true; + + if (reverse) { + int start = Math.max(0, editText.getSelectionStart() - 1); + editText.setSelection(start, editText.getSelectionEnd()); + } else { + int end = Math.min(editText.length(), editText.getSelectionEnd() - 1); + editText.setSelection(end, end); + } + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (editText.getSelectionStart() == editText.getSelectionEnd()) + reverse = false; + + if (reverse) { + int start = Math.max(0, editText.getSelectionStart() + 1); + editText.setSelection(start, editText.getSelectionEnd()); + } else { + int end = Math.min(editText.length(), editText.getSelectionEnd() + 1); + editText.setSelection(end, end); + } + break; + } + } else { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + int start = Math.max(0, editText.getSelectionStart() - 1); + editText.setSelection(start, start); + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + int end = Math.min(editText.length(), editText.getSelectionEnd() + 1); + editText.setSelection(end, end); + break; + } + } + return false; + } +} diff --git a/app/src/main/java/de/kuschku/util/niohelpers/Helper.java b/app/src/main/java/de/kuschku/util/niohelpers/Helper.java new file mode 100644 index 000000000..30a6250a8 --- /dev/null +++ b/app/src/main/java/de/kuschku/util/niohelpers/Helper.java @@ -0,0 +1,32 @@ +package de.kuschku.util.niohelpers; + +import android.util.Log; + +public class Helper { + // Making default constructor invisible + private Helper() { + + } + + public static void printHexDump(byte[] data) { + Log.e("HexDump", "Hexdump following: "); + String bytes = ""; + String text = ""; + int i; + for (i = 0; i < data.length; i++) { + bytes += String.format("%02x ", data[i]); + text += encodeChar(data[1]); + if (i > 0 && (i + 1) % 8 == 0) { + Log.e("HexDump", String.format("%08x ", i - 7) + bytes + text); + bytes = ""; + text = ""; + } + } + Log.e("HexDump", String.format("%08x ", i - 7) + bytes + text); + } + + private static char encodeChar(byte data) { + if (data < 127 && data > 32) return (char) data; + else return '.'; + } +} diff --git a/app/src/main/java/de/kuschku/util/observables/callbacks/GeneralCallback.java b/app/src/main/java/de/kuschku/util/observables/callbacks/GeneralCallback.java new file mode 100644 index 000000000..1cf2fa624 --- /dev/null +++ b/app/src/main/java/de/kuschku/util/observables/callbacks/GeneralCallback.java @@ -0,0 +1,5 @@ +package de.kuschku.util.observables.callbacks; + +public interface GeneralCallback { + void notifyChanged(); +} diff --git a/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralCallbackWrapper.java b/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralCallbackWrapper.java new file mode 100644 index 000000000..1cb1f28d6 --- /dev/null +++ b/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralCallbackWrapper.java @@ -0,0 +1,28 @@ +package de.kuschku.util.observables.callbacks.wrappers; + +import java.util.HashSet; +import java.util.Set; + +import de.kuschku.util.observables.IObservable; +import de.kuschku.util.observables.callbacks.GeneralCallback; + +public class GeneralCallbackWrapper implements IObservable<GeneralCallback>, GeneralCallback { + Set<GeneralCallback> callbacks = new HashSet<>(); + + @Override + public void notifyChanged() { + for (GeneralCallback callback : callbacks) { + callback.notifyChanged(); + } + } + + @Override + public void addCallback(GeneralCallback callback) { + callbacks.add(callback); + } + + @Override + public void removeCallback(GeneralCallback callback) { + callbacks.remove(callback); + } +} diff --git a/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralUICallbackWrapper.java b/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralUICallbackWrapper.java new file mode 100644 index 000000000..fd1982fe7 --- /dev/null +++ b/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralUICallbackWrapper.java @@ -0,0 +1,42 @@ +package de.kuschku.util.observables.callbacks.wrappers; + +import de.kuschku.util.observables.callbacks.UICallback; + +public abstract class GeneralUICallbackWrapper implements UICallback { + public abstract void notifyChanged(); + + @Override + public void notifyItemInserted(int position) { + notifyChanged(); + } + + @Override + public void notifyItemChanged(int position) { + notifyChanged(); + } + + @Override + public void notifyItemRemoved(int position) { + notifyChanged(); + } + + @Override + public void notifyItemMoved(int from, int to) { + notifyChanged(); + } + + @Override + public void notifyItemRangeInserted(int position, int count) { + notifyChanged(); + } + + @Override + public void notifyItemRangeChanged(int position, int count) { + notifyChanged(); + } + + @Override + public void notifyItemRangeRemoved(int position, int count) { + notifyChanged(); + } +} diff --git a/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java b/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java new file mode 100644 index 000000000..6485c2fd2 --- /dev/null +++ b/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java @@ -0,0 +1,83 @@ +package de.kuschku.util.observables.lists; + +import android.support.annotation.NonNull; + +import de.kuschku.util.observables.IObservable; +import de.kuschku.util.observables.callbacks.UICallback; +import de.kuschku.util.observables.callbacks.UIChildCallback; +import de.kuschku.util.observables.callbacks.UIChildParentCallback; +import de.kuschku.util.observables.callbacks.UIParentCallback; +import de.kuschku.util.observables.callbacks.wrappers.MultiUIChildParentCallback; +import de.kuschku.util.observables.callbacks.wrappers.ParentUICallbackWrapper; + +public class ChildParentObservableSortedList<T extends IObservable<UIChildCallback>> extends ObservableSortedList<T> { + + private MultiUIChildParentCallback callback = MultiUIChildParentCallback.of(); + + public ChildParentObservableSortedList(@NonNull Class<T> cl, @NonNull ItemComparator<T> comparator) { + super(cl, comparator); + registerCallbacks(); + } + + public ChildParentObservableSortedList(@NonNull Class<T> cl, @NonNull ItemComparator<T> comparator, boolean reverse) { + super(cl, comparator, reverse); + registerCallbacks(); + } + + private void registerCallbacks() { + super.addCallback(new MyWrapper(callback)); + } + + public void addChildParentCallback(@NonNull UIChildParentCallback callback) { + this.callback.addCallback(callback); + } + + public void removeChildParentCallback(@NonNull UIChildParentCallback callback) { + this.callback.removeCallback(callback); + } + + private class MyWrapper extends ParentUICallbackWrapper { + public MyWrapper(@NonNull UIParentCallback wrapped) { + super(wrapped); + } + + @Override + public void notifyItemInserted(int position) { + super.notifyItemInserted(position); + get(position).addCallback(callback); + } + + @Override + public void notifyItemChanged(int position) { + super.notifyItemChanged(position); + } + + @Override + public void notifyItemRemoved(int position) { + super.notifyItemRemoved(position); + } + + @Override + public void notifyItemMoved(int from, int to) { + super.notifyItemMoved(from, to); + } + + @Override + public void notifyItemRangeInserted(int position, int count) { + super.notifyItemRangeInserted(position, count); + for (int i = position; i < position + count; i++) { + get(position).addCallback(callback); + } + } + + @Override + public void notifyItemRangeChanged(int position, int count) { + super.notifyItemRangeChanged(position, count); + } + + @Override + public void notifyItemRangeRemoved(int position, int count) { + super.notifyItemRangeRemoved(position, count); + } + } +} diff --git a/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java b/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java index 97901b4ed..b6bab5cd4 100644 --- a/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java +++ b/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java @@ -20,7 +20,7 @@ import static de.kuschku.util.AndroidAssert.assertTrue; public class ObservableSortedList<T> implements IObservableList<UICallback, T> { @NonNull - public final SortedList<T> list; + private final SortedList<T> list; private final boolean reverse; @NonNull @@ -120,7 +120,7 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> { @Override public int lastIndexOf(Object object) { - return 0; + return indexOf(object); } @NonNull @@ -138,17 +138,28 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> { @Nullable @Override public T remove(int location) { - return null; + T item = list.get(location); + list.remove(item); + return item; } @Override public boolean remove(Object object) { - return false; + try { + list.remove((T) object); + return true; + } catch (ClassCastException e) { + return false; + } } @Override public boolean removeAll(@NonNull Collection<?> collection) { - return false; + boolean result = true; + for (Object o : collection) { + result &= remove(o); + } + return result; } @Override @@ -164,7 +175,7 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> { @Override public int size() { - return 0; + return list.size(); } @NonNull @@ -193,6 +204,10 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> { throw new MaterialDialog.NotImplementedException("Not implemented"); } + public void notifyItemChanged(int position) { + callback.notifyItemChanged(position); + } + public interface ItemComparator<T> { int compare(T o1, T o2); diff --git a/app/src/main/java/de/kuschku/util/ui/MessageUtil.java b/app/src/main/java/de/kuschku/util/ui/MessageUtil.java new file mode 100644 index 000000000..75a7e02cd --- /dev/null +++ b/app/src/main/java/de/kuschku/util/ui/MessageUtil.java @@ -0,0 +1,167 @@ +package de.kuschku.util.ui; + +import android.graphics.Typeface; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; + +public class MessageUtil { + // Transparent in ARGB + private static final int COLOR_TRANSPARENT = 0x00000000; + + /** + * Parse mIRC style codes in IrcMessage + */ + public static SpannableString parseStyleCodes(ThemeUtil themeUtil, String content, boolean parse) { + if (!parse) { + return new SpannableString(content + .replaceAll("\\x02","") + .replaceAll("\\x0F","") + .replaceAll("\\x1D","") + .replaceAll("\\x1F","") + .replaceAll("\\x03[0-9]{1,2}(,[0-9]{1,2})?","") + .replaceAll("\\x03","")); + } + + final char boldIndicator = 2; + final char normalIndicator = 15; + final char italicIndicator = 29; + final char underlineIndicator = 31; + final char colorIndicator = 3; + + if (content.indexOf(boldIndicator) == -1 + && content.indexOf(italicIndicator) == -1 + && content.indexOf(underlineIndicator) == -1 + && content.indexOf(colorIndicator) == -1) + return new SpannableString(content); + + SpannableStringBuilder newString = new SpannableStringBuilder(content); + + int start, end, endSearchOffset, startIndicatorLength, style, fg, bg; + while (true) { + content = newString.toString(); + start = -1; + end = -1; + startIndicatorLength = 1; + style = 0; + fg = -1; + bg = -1; + + // Colors? + start = content.indexOf(colorIndicator); + + if (start != -1) { + // Note that specifying colour codes here is optional, as the same indicator will cancel existing colours + endSearchOffset = start + 1; + if (endSearchOffset < content.length()) { + if (Character.isDigit(content.charAt(endSearchOffset))) { + if (endSearchOffset + 1 < content.length() && Character.isDigit(content.charAt(endSearchOffset + 1))) { + fg = Integer.parseInt(content.substring(endSearchOffset, endSearchOffset + 2)); + endSearchOffset += 2; + } else { + fg = Integer.parseInt(content.substring(endSearchOffset, endSearchOffset + 1)); + endSearchOffset += 1; + } + + if (endSearchOffset < content.length() && content.charAt(endSearchOffset) == ',') { + if (endSearchOffset + 1 < content.length() && Character.isDigit(content.charAt(endSearchOffset + 1))) { + endSearchOffset++; + if (endSearchOffset + 1 < content.length() && Character.isDigit(content.charAt(endSearchOffset + 1))) { + bg = Integer.parseInt(content.substring(endSearchOffset, endSearchOffset + 2)); + endSearchOffset += 2; + } else { + bg = Integer.parseInt(content.substring(endSearchOffset, endSearchOffset + 1)); + endSearchOffset += 1; + } + } + } + } + } + startIndicatorLength = endSearchOffset - start; + + end = content.indexOf(colorIndicator, endSearchOffset); + } + + if (start == -1) { + start = content.indexOf(boldIndicator); + if (start != -1) { + end = content.indexOf(boldIndicator, start + 1); + style = Typeface.BOLD; + } + } + + if (start == -1) { + start = content.indexOf(italicIndicator); + if (start != -1) { + end = content.indexOf(italicIndicator, start + 1); + style = Typeface.ITALIC; + } + } + + if (start == -1) { + start = content.indexOf(underlineIndicator); + if (start != -1) { + end = content.indexOf(underlineIndicator, start + 1); + style = -1; + } + } + + if (start == -1) + break; + + int norm = content.indexOf(normalIndicator, start + 1); + if (norm != -1 && (end == -1 || norm < end)) + end = norm; + + if (end == -1) + end = content.length(); + + if (end - (start + startIndicatorLength) > 0) { + // Only set spans if there's any text between start & end + if (style == -1) { + newString.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } else { + newString.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } + + if (fg != -1 && themeUtil.colors.mircColors[fg] != COLOR_TRANSPARENT) { + newString.setSpan(new ForegroundColorSpan(themeUtil.colors.mircColors[fg]), start, end, + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } + if (bg != -1 && themeUtil.colors.mircColors[fg] != COLOR_TRANSPARENT) { + newString.setSpan(new BackgroundColorSpan(themeUtil.colors.mircColors[fg]), start, end, + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } + } + + // Intentionally don't remove "normal" indicators or color here, as they are multi-purpose + if (end < content.length() && (content.charAt(end) == boldIndicator + || content.charAt(end) == italicIndicator + || content.charAt(end) == underlineIndicator)) + newString.delete(end, end + 1); + + newString.delete(start, start + startIndicatorLength); + } + + // NOW we remove the "normal" and color indicator + while (true) { + content = newString.toString(); + int normPos = content.indexOf(normalIndicator); + if (normPos != -1) + newString.delete(normPos, normPos + 1); + + int colorPos = content.indexOf(colorIndicator); + if (colorPos != -1) + newString.delete(colorPos, colorPos + 1); + + if (normPos == -1 && colorPos == -1) + break; + } + + return new SpannableString(newString); + } +} diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml new file mode 100644 index 000000000..a0a00ed3b --- /dev/null +++ b/app/src/main/res/layout/activity_chat.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.sothree.slidinguppanel.SlidingUpPanelLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/sliding_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="bottom" + android:orientation="vertical" + app:umanoPanelHeight="?attr/actionBarSize" + app:umanoShadowHeight="4dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <include layout="@layout/toolbar" /> + + <include layout="@layout/content_main" /> + + </LinearLayout> + + <include layout="@layout/slider_main" /> + +</com.sothree.slidinguppanel.SlidingUpPanelLayout> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index caa8472e0..000000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<android.support.v4.widget.DrawerLayout 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/drawer_left" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="true" - tools:context=".ui.chat.ChatActivity"> - - <com.sothree.slidinguppanel.SlidingUpPanelLayout - android:id="@+id/sliding_layout" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="bottom" - android:orientation="vertical" - app:umanoPanelHeight="?attr/actionBarSize" - app:umanoShadowHeight="4dp"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <include layout="@layout/toolbar" /> - - <include layout="@layout/content_main" /> - - </LinearLayout> - - <include layout="@layout/slider_main" /> - - </com.sothree.slidinguppanel.SlidingUpPanelLayout> - - <include layout="@layout/drawer" /> - -</android.support.v4.widget.DrawerLayout> diff --git a/app/src/main/res/layout/core_dialog.xml b/app/src/main/res/layout/core_dialog.xml deleted file mode 100644 index a8c5541f0..000000000 --- a/app/src/main/res/layout/core_dialog.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <EditText - android:id="@+id/server" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:inputType="textUri" /> - - <EditText - android:id="@+id/port" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:inputType="number" /> - -</LinearLayout> diff --git a/app/src/main/res/layout/dialog_address.xml b/app/src/main/res/layout/dialog_address.xml new file mode 100644 index 000000000..0942bf2b9 --- /dev/null +++ b/app/src/main/res/layout/dialog_address.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <android.support.v7.widget.AppCompatEditText + android:id="@+id/host" + android:hint="Hostname" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textUri" /> + </android.support.design.widget.TextInputLayout> + + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <android.support.v7.widget.AppCompatEditText + android:id="@+id/port" + android:hint="Port" + android:text="4242" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="number" /> + </android.support.design.widget.TextInputLayout> +</LinearLayout> diff --git a/app/src/main/res/layout/dialog_login.xml b/app/src/main/res/layout/dialog_login.xml new file mode 100644 index 000000000..fedef624f --- /dev/null +++ b/app/src/main/res/layout/dialog_login.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <android.support.v7.widget.AppCompatEditText + android:id="@+id/username" + android:hint="Username" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="text" /> + </android.support.design.widget.TextInputLayout> + + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <android.support.v7.widget.AppCompatEditText + android:id="@+id/password" + android:hint="Password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" /> + </android.support.design.widget.TextInputLayout> +</LinearLayout> diff --git a/app/src/main/res/layout/drawer.xml b/app/src/main/res/layout/drawer.xml deleted file mode 100644 index 9361ba5af..000000000 --- a/app/src/main/res/layout/drawer.xml +++ /dev/null @@ -1,47 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<android.support.design.widget.NavigationView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/navigation_left" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_gravity="start"> - - <LinearLayout - android:layout_width="320dp" - android:layout_height="match_parent" - android:orientation="vertical"> - - <RelativeLayout - android:id="@+id/navigation_header_container" - android:layout_width="match_parent" - android:layout_height="72dp" - android:orientation="vertical"> - - <android.support.v7.widget.AppCompatImageView - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerCrop" - android:src="@drawable/bg" /> - - <android.support.v7.widget.Toolbar - android:id="@+id/buffer_view_toolbar" - android:layout_width="match_parent" - android:layout_height="72dp" - android:layout_alignParentBottom="true" - android:theme="@style/AppTheme.Light"> - - <android.support.v7.widget.AppCompatSpinner - android:id="@+id/buffer_view_spinner" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - - </android.support.v7.widget.Toolbar> - - </RelativeLayout> - - <android.support.v7.widget.RecyclerView - android:layout_width="wrap_content" - android:layout_height="0dip" - android:layout_weight="1" /> - </LinearLayout> - -</android.support.design.widget.NavigationView> diff --git a/app/src/main/res/layout/login_dialog.xml b/app/src/main/res/layout/login_dialog.xml deleted file mode 100644 index b30ba636d..000000000 --- a/app/src/main/res/layout/login_dialog.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <EditText - android:id="@+id/username" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:inputType="text" /> - - <EditText - android:id="@+id/password" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:inputType="textPassword" /> - -</LinearLayout> diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml index 67aacd354..8cbf7e23c 100644 --- a/app/src/main/res/layout/toolbar.xml +++ b/app/src/main/res/layout/toolbar.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android" +<android.support.design.widget.AppBarLayout + xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/layout/widget_chatmessage.xml b/app/src/main/res/layout/widget_chatmessage.xml index f114000c7..b365486e8 100644 --- a/app/src/main/res/layout/widget_chatmessage.xml +++ b/app/src/main/res/layout/widget_chatmessage.xml @@ -19,7 +19,6 @@ android:layout_height="wrap_content" android:layout_marginEnd="@dimen/message_horizontal" android:layout_marginRight="@dimen/message_horizontal" - android:text="10:08" android:typeface="monospace" /> <TextView @@ -27,6 +26,5 @@ android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:clickable="true" - android:text="NK33 well, the issue is I need the main engines to tilt 90 degrees for vertical takeoff and landing" /> + android:clickable="true" /> </LinearLayout> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 3fc5c10a5..569a42b14 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ <resources> <!-- Base application theme. --> - <style name="AppTheme" parent="Theme.AppCompat.NoActionBar"> + <style name="AppTheme" parent="MaterialDrawerTheme.TranslucentStatus"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> @@ -12,7 +12,7 @@ <item name="chatlineBackground">@color/md_dark_cards</item> </style> - <style name="AppTheme.Light" parent="Theme.AppCompat.Light.NoActionBar"> + <style name="AppTheme.Light" parent="MaterialDrawerTheme.Light.DarkToolbar.TranslucentStatus"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 81bf17eb7..3c39fb034 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -85,6 +85,23 @@ <item name="senderColorE">#b39775</item> <item name="senderColorF">#3176b3</item> + <item name="mircColor0">#ffffff</item> + <item name="mircColor1">#000000</item> + <item name="mircColor2">#000080</item> + <item name="mircColor3">#008000</item> + <item name="mircColor4">#ff0000</item> + <item name="mircColor5">#800000</item> + <item name="mircColor6">#800080</item> + <item name="mircColor7">#ffa500</item> + <item name="mircColor8">#ffff00</item> + <item name="mircColor9">#00ff00</item> + <item name="mircColorA">#008080</item> + <item name="mircColorB">#00ffff</item> + <item name="mircColorC">#4169e1</item> + <item name="mircColorD">#ff00ff</item> + <item name="mircColorE">#808080</item> + <item name="mircColorF">#c0c0c0</item> + <item name="colorForeground">@color/md_light_primary_text</item> <item name="colorForegroundHighlight">@color/md_light_primary_text</item> <item name="colorForegroundSecondary">@color/md_light_secondary</item> -- GitLab