From dfc17fcfc2d938cb8d2f9cc596a44cc5684da18a Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Mon, 12 Sep 2016 02:20:16 +0200 Subject: [PATCH] Added nick list, optimized sorting behaviour --- app/build.gradle | 10 +- .../de/kuschku/libquassel/CoreConnection.java | 5 +- .../de/kuschku/libquassel/client/Client.java | 16 +- .../libquassel/ssl/QuasselTrustManager.java | 14 +- .../syncables/SyncableRegistry.java | 2 + .../serializers/CoreInfoSerializer.java | 86 +++++++ .../serializers/NetworkSerializer.java | 26 +- .../syncables/types/abstracts/ANetwork.java | 6 - .../types/impl/BufferViewManager.java | 2 +- .../syncables/types/impl/CoreInfo.java | 54 ++++ .../syncables/types/impl/IrcChannel.java | 34 +-- .../syncables/types/impl/IrcUser.java | 26 +- .../syncables/types/impl/Network.java | 63 +++-- .../syncables/types/interfaces/QNetwork.java | 5 +- .../syncables/types/invokers/INetwork.java | 3 - .../quasseldroid_ng/ui/chat/MainActivity.java | 45 ++-- .../chat/dialogs/CoreInfoDialogBuilder.java | 114 +++++++++ .../chat/drawer/BufferViewConfigAdapter.java | 2 +- .../ui/chat/nicklist/NickListAdapter.java | 181 +++++++++++++ .../coresettings/network/AllNetworksItem.java | 10 +- .../quasseldroid_ng/ui/theme/ThemeUtil.java | 23 +- .../de/kuschku/util/CompatibilityUtils.java | 3 +- .../util/niohelpers/WrappedChannel.java | 7 +- .../lists/ObservableSortedList.java | 4 +- app/src/main/res/drawable/badge.xml | 2 +- .../main/res/layout-w720dp/activity_main.xml | 93 ------- app/src/main/res/layout/activity_main.xml | 12 + app/src/main/res/layout/dialog_coreinfo.xml | 240 ++++++++++++++++++ app/src/main/res/layout/widget_actionbar.xml | 22 +- .../main/res/layout/widget_channel_mode.xml | 23 +- app/src/main/res/layout/widget_nick.xml | 75 ++++++ .../main/res/layout/widget_settings_alias.xml | 2 +- app/src/main/res/menu/chat.xml | 4 + app/src/main/res/values/strings_actions.xml | 2 + .../main/res/values/strings_coresettings.xml | 9 + app/src/main/res/values/styles.xml | 21 ++ 36 files changed, 1017 insertions(+), 229 deletions(-) create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/serializers/CoreInfoSerializer.java create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/dialogs/CoreInfoDialogBuilder.java create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicklist/NickListAdapter.java delete mode 100644 app/src/main/res/layout-w720dp/activity_main.xml create mode 100644 app/src/main/res/layout/dialog_coreinfo.xml create mode 100644 app/src/main/res/layout/widget_nick.xml diff --git a/app/build.gradle b/app/build.gradle index 38fd8549e..e5b043e51 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -117,6 +117,10 @@ android { debug { applicationIdSuffix ".debug" + + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions { @@ -173,14 +177,14 @@ dependencies { compile 'com.android.support:preference-v14:24.2.0' compile 'com.android.support:cardview-v7:24.2.0' + // SSL + compile 'org.cryptacular:cryptacular:1.1.0' + // Reactive Libs compile 'io.reactivex:rxandroid:1.2.1' compile 'io.reactivex:rxjava:1.1.6' compile 'com.jakewharton.rxbinding:rxbinding:0.4.0' - compile 'com.jakewharton.rxbinding:rxbinding-support-v4:0.4.0' compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.4.0' - compile 'com.jakewharton.rxbinding:rxbinding-design:0.4.0' - compile 'com.jakewharton.rxbinding:rxbinding-recyclerview-v7:0.4.0' // Crashreports compile 'ch.acra:acra:4.9.0' diff --git a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java index eead77323..251b900be 100644 --- a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java +++ b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java @@ -40,6 +40,8 @@ import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import javax.security.cert.X509Certificate; + import de.kuschku.libquassel.client.Client; import de.kuschku.libquassel.client.ClientData; import de.kuschku.libquassel.events.ConnectionChangeEvent; @@ -95,6 +97,7 @@ public class CoreConnection { private Socket socket; @NonNull private ConnectionChangeEvent.Status status = ConnectionChangeEvent.Status.DISCONNECTED; + private X509Certificate[] peerCertificateChain; public CoreConnection(@NonNull final ServerAddress address, @NonNull final ClientData clientData, @@ -234,7 +237,7 @@ public class CoreConnection { private void setSSL(boolean supportsSSL) { if (supportsSSL) { try { - channel = WrappedChannel.withSSL(getChannel(), certificateManager, address); + channel = WrappedChannel.withSSL(getChannel(), certificateManager, address, client::setCertificateChain); } catch (Exception e) { if (e.getCause() instanceof UnknownCertificateException) { busProvider.sendEvent(new UnknownCertificateEvent((UnknownCertificateException) e.getCause())); diff --git a/app/src/main/java/de/kuschku/libquassel/client/Client.java b/app/src/main/java/de/kuschku/libquassel/client/Client.java index 1da25b58c..f616da732 100644 --- a/app/src/main/java/de/kuschku/libquassel/client/Client.java +++ b/app/src/main/java/de/kuschku/libquassel/client/Client.java @@ -26,6 +26,7 @@ import android.support.annotation.Nullable; import android.util.Log; import android.util.Pair; +import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -58,6 +59,7 @@ import de.kuschku.libquassel.syncables.types.interfaces.QBufferViewManager; import de.kuschku.libquassel.syncables.types.interfaces.QIgnoreListManager; import de.kuschku.libquassel.syncables.types.interfaces.QNetwork; import de.kuschku.libquassel.syncables.types.interfaces.QNetworkConfig; +import de.kuschku.libquassel.syncables.types.interfaces.QSyncableObject; import de.kuschku.util.buffermetadata.BufferMetaDataManager; import static de.kuschku.util.AndroidAssert.assertNotNull; @@ -90,6 +92,7 @@ public class Client extends AClient { private long latency; private ConnectionChangeEvent.Status connectionStatus; private int r = 1; + private X509Certificate[] certificateChain; public Client(@NonNull BusProvider provider, @NonNull BacklogStorage backlogStorage, @NonNull BufferMetaDataManager metaDataManager, String coreId) { this.coreId = coreId; @@ -178,8 +181,8 @@ public class Client extends AClient { } @Override - public void ___objectRenamed__(String type, String oldName, String newName) { - + public void ___objectRenamed__(String type, String newName, String oldName) { + ((QSyncableObject) unsafe_getObjectByIdentifier(type, oldName)).setObjectName(newName); } public synchronized ConnectionChangeEvent.Status connectionStatus() { @@ -312,6 +315,7 @@ public class Client extends AClient { requestInitObject("AliasManager", ""); requestInitObject("NetworkConfig", "GlobalNetworkConfig"); requestInitObject("IgnoreListManager", ""); + requestInitObject("CoreInfo", ""); //sendInitRequest("TransferManager", ""); // This thing never gets sent... @@ -480,4 +484,12 @@ public class Client extends AClient { public String coreId() { return coreId; } + + public X509Certificate[] certificateChain() { + return certificateChain; + } + + public void setCertificateChain(X509Certificate[] certificateChain) { + this.certificateChain = certificateChain; + } } diff --git a/app/src/main/java/de/kuschku/libquassel/ssl/QuasselTrustManager.java b/app/src/main/java/de/kuschku/libquassel/ssl/QuasselTrustManager.java index 461d2c5eb..3a6edbe89 100644 --- a/app/src/main/java/de/kuschku/libquassel/ssl/QuasselTrustManager.java +++ b/app/src/main/java/de/kuschku/libquassel/ssl/QuasselTrustManager.java @@ -34,6 +34,7 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import de.kuschku.util.accounts.ServerAddress; +import de.kuschku.util.backports.Consumer; import de.kuschku.util.certificates.CertificateUtils; public class QuasselTrustManager implements X509TrustManager { @@ -43,29 +44,31 @@ public class QuasselTrustManager implements X509TrustManager { private final CertificateManager certificateManager; @NonNull private final ServerAddress address; + private final Consumer<X509Certificate[]> callback; - public QuasselTrustManager(@NonNull X509TrustManager wrapped, @NonNull CertificateManager certificateManager, @NonNull ServerAddress address) { + public QuasselTrustManager(@NonNull X509TrustManager wrapped, @NonNull CertificateManager certificateManager, @NonNull ServerAddress address, Consumer<X509Certificate[]> callback) { this.wrapped = wrapped; this.certificateManager = certificateManager; this.address = address; + this.callback = callback; } @NonNull - public static QuasselTrustManager fromFactory(@NonNull TrustManagerFactory factory, @NonNull CertificateManager certificateManager, @NonNull ServerAddress address) throws GeneralSecurityException { + public static QuasselTrustManager fromFactory(@NonNull TrustManagerFactory factory, @NonNull CertificateManager certificateManager, @NonNull ServerAddress address, Consumer<X509Certificate[]> callback) throws GeneralSecurityException { TrustManager[] managers = factory.getTrustManagers(); for (TrustManager manager : managers) { if (manager instanceof X509TrustManager) { - return new QuasselTrustManager((X509TrustManager) manager, certificateManager, address); + return new QuasselTrustManager((X509TrustManager) manager, certificateManager, address, callback); } } throw new GeneralSecurityException("Couldn’t find trustmanager provided by factory"); } @NonNull - public static QuasselTrustManager fromDefault(@NonNull CertificateManager certificateManager, @NonNull ServerAddress address) throws GeneralSecurityException { + public static QuasselTrustManager fromDefault(@NonNull CertificateManager certificateManager, @NonNull ServerAddress address, Consumer<X509Certificate[]> callback) throws GeneralSecurityException { TrustManagerFactory factory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); factory.init((KeyStore) null); - return fromFactory(factory, certificateManager, address); + return fromFactory(factory, certificateManager, address, callback); } @Override @@ -83,6 +86,7 @@ public class QuasselTrustManager implements X509TrustManager { } catch (CertificateException e) { certificateManager.checkTrusted(chain[0], address); } + callback.apply(chain); } @Override 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 a5ba19693..cbfa0d5a9 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java @@ -35,6 +35,7 @@ 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.CoreInfoSerializer; import de.kuschku.libquassel.syncables.serializers.IdentitySerializer; import de.kuschku.libquassel.syncables.serializers.IgnoreListManagerSerializer; import de.kuschku.libquassel.syncables.serializers.IrcChannelSerializer; @@ -59,6 +60,7 @@ public class SyncableRegistry { map.put("Network", NetworkSerializer.get()); map.put("NetworkConfig", NetworkConfigSerializer.get()); map.put("AliasManager", AliasManagerSerializer.get()); + map.put("CoreInfo", CoreInfoSerializer.get()); } private SyncableRegistry() { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/CoreInfoSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/CoreInfoSerializer.java new file mode 100644 index 000000000..7adba85fb --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/CoreInfoSerializer.java @@ -0,0 +1,86 @@ +/* + * QuasselDroid - Quassel client for Android + * Copyright (C) 2016 Janne Koschinski + * Copyright (C) 2016 Ken Børge Viktil + * Copyright (C) 2016 Magnus Fjell + * Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.libquassel.syncables.serializers; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.HashMap; +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.impl.CoreInfo; + +import static de.kuschku.util.AndroidAssert.assertNotNull; + +@SuppressWarnings("unchecked") +public class CoreInfoSerializer implements ObjectSerializer<CoreInfo> { + @NonNull + private static final CoreInfoSerializer serializer = new CoreInfoSerializer(); + + private CoreInfoSerializer() { + + } + + @NonNull + public static CoreInfoSerializer get() { + return serializer; + } + + @Nullable + @Override + public Map<String, QVariant<Object>> toVariantMap(@NonNull CoreInfo data) { + Map<String, QVariant<Object>> map = new HashMap<>(); + map.put("coreData", (QVariant<Object>) data.coreData()); + return map; + } + + @NonNull + @Override + public CoreInfo fromDatastream(@NonNull Map<String, QVariant> map) { + return fromLegacy(map); + } + + @NonNull + @Override + public CoreInfo fromLegacy(@NonNull Map<String, QVariant> map) { + QVariant<Map<String, QVariant>> data = map.get("coreData"); + assertNotNull(data); + return new CoreInfo( + data.data + ); + } + + @Nullable + @Override + public CoreInfo 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 9eda5e7dd..ed67d9c89 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 @@ -28,7 +28,6 @@ import org.joda.time.DateTime; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -71,22 +70,9 @@ public class NetworkSerializer implements ObjectSerializer<Network> { public Network fromDatastream(@NonNull Map<String, QVariant> map) { final Map<String, QVariant<Map<String, QVariant<List>>>> usersAndChannels = ((Map<String, QVariant<Map<String, QVariant<List>>>>) map.get("IrcUsersAndChannels").data); - final List<QIrcChannel> channels = extractChannels(QVariant.orNull(usersAndChannels.get("Channels"))); - final List<QIrcUser> users = extractUsers(QVariant.orNull(usersAndChannels.get("Users"))); - - final Map<String, QIrcChannel> channelMap = new HashMap<>(channels.size()); - for (QIrcChannel channel : channels) { - channelMap.put(channel.name(), channel); - } - - final Map<String, QIrcUser> userMap = new HashMap<>(users.size()); - for (QIrcUser user : users) { - userMap.put(user.nick(), user); - } - return new Network( - channelMap, - userMap, + extractChannels(QVariant.orNull(usersAndChannels.get("Channels"))), + extractUsers(QVariant.orNull(usersAndChannels.get("Users"))), (List<NetworkServer>) map.get("ServerList").data, StringObjectMapSerializer.<String>get().fromLegacy((Map<String, QVariant>) map.get("Supports").data), (int) map.get("connectionState").data, @@ -166,15 +152,15 @@ public class NetworkSerializer implements ObjectSerializer<Network> { final Map<String, QVariant<Map<String, QVariant<Map<String, QVariant>>>>> usersAndChannels = ((QVariant<Map<String, QVariant<Map<String, QVariant<Map<String, QVariant>>>>>>) map.get("IrcUsersAndChannels")).data; final Map<String, QVariant<Map<String, QVariant>>> wrappedChannels = usersAndChannels.get("channels").data; final Map<String, QVariant<Map<String, QVariant>>> wrappedUsers = usersAndChannels.get("users").data; - final Map<String, QIrcChannel> channels = new HashMap<>(wrappedChannels.size()); + final List<QIrcChannel> channels = new ArrayList<>(wrappedChannels.size()); for (Map.Entry<String, QVariant<Map<String, QVariant>>> entry : wrappedChannels.entrySet()) { final QIrcChannel ircChannel = IrcChannelSerializer.get().fromLegacy(entry.getValue().data); - channels.put(ircChannel.name(), ircChannel); + channels.add(ircChannel); } - final Map<String, QIrcUser> users = new HashMap<>(wrappedUsers.size()); + final List<QIrcUser> users = new ArrayList<>(wrappedUsers.size()); for (Map.Entry<String, QVariant<Map<String, QVariant>>> entry : wrappedUsers.entrySet()) { final QIrcUser ircUser = IrcUserSerializer.get().fromLegacy(entry.getValue().data); - users.put(ircUser.nick(), ircUser); + users.add(ircUser); } final Map<String, String> supports = StringObjectMapSerializer.<String>get().fromLegacy((Map<String, QVariant>) map.get("Supports").data); return new Network( diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/abstracts/ANetwork.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/abstracts/ANetwork.java index 4d8cda108..0e75c5e03 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/abstracts/ANetwork.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/abstracts/ANetwork.java @@ -207,12 +207,6 @@ public abstract class ANetwork extends SyncableObject<QNetwork> implements QNetw syncVar("addIrcChannel", channel); } - @Override - public void ircUserNickChanged(String oldnick, String newnick) { - _ircUserNickChanged(oldnick, newnick); - syncVar("ircUserNickChanged", newnick); - } - @Override public void connect() { _connect(); diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/BufferViewManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/BufferViewManager.java index b210b665a..28d536f3d 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/BufferViewManager.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/BufferViewManager.java @@ -45,7 +45,7 @@ public class BufferViewManager extends ABufferViewManager { final ObservableSortedList<QBufferViewConfig> list = new ObservableSortedList<>(QBufferViewConfig.class, new ObservableSortedList.ItemComparator<QBufferViewConfig>() { @Override public int compare(QBufferViewConfig o1, QBufferViewConfig o2) { - return o1.bufferViewName().compareTo(o2.bufferViewName()); + return o1.bufferViewName().compareToIgnoreCase(o2.bufferViewName()); } @Override diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/CoreInfo.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/CoreInfo.java index 3a773ddc1..97160a8e3 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/CoreInfo.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/CoreInfo.java @@ -23,6 +23,8 @@ package de.kuschku.libquassel.syncables.types.impl; import android.support.annotation.NonNull; +import org.joda.time.DateTime; + import java.util.Map; import de.kuschku.libquassel.BusProvider; @@ -34,6 +36,18 @@ import de.kuschku.libquassel.syncables.types.interfaces.QCoreInfo; public class CoreInfo extends ACoreInfo { private Map<String, QVariant> coreData; + private int sessionConnectedClients; + + private String quasselVersion; + + private String quasselBuildDate; + + private DateTime startTime; + + public CoreInfo(Map<String, QVariant> coreData) { + _setCoreData(coreData); + } + @Override public Map<String, QVariant> coreData() { return coreData; @@ -42,6 +56,19 @@ public class CoreInfo extends ACoreInfo { @Override public void _setCoreData(Map<String, QVariant> coreData) { this.coreData = coreData; + + QVariant sessionConnectedClients1 = coreData.remove("sessionConnectedClients"); + this.sessionConnectedClients = sessionConnectedClients1 != null ? (int) sessionConnectedClients1.data : -1; + + QVariant quasselVersion1 = coreData.remove("quasselVersion"); + this.quasselVersion = quasselVersion1 != null ? (String) quasselVersion1.data : null; + + QVariant quasselBuildDate1 = coreData.remove("quasselBuildDate"); + this.quasselBuildDate = quasselBuildDate1 != null ? (String) quasselBuildDate1.data : null; + + QVariant startTime1 = coreData.remove("startTime"); + this.startTime = startTime1 != null ? (DateTime) startTime1.data : null; + _update(); } @@ -60,4 +87,31 @@ public class CoreInfo extends ACoreInfo { super.init(objectName, provider, client); client.setCoreInfo(this); } + + public int sessionConnectedClients() { + return sessionConnectedClients; + } + + public String quasselVersion() { + return quasselVersion; + } + + public String quasselBuildDate() { + return quasselBuildDate; + } + + public DateTime startTime() { + return startTime; + } + + @Override + public String toString() { + return "CoreInfo{" + + "sessionConnectedClients=" + sessionConnectedClients + + ", quasselVersion='" + quasselVersion + '\'' + + ", quasselBuildDate='" + quasselBuildDate + '\'' + + ", startTime=" + startTime + + ", coreData=" + coreData + + '}'; + } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/IrcChannel.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/IrcChannel.java index 2c8398e0f..fcabcef88 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/IrcChannel.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/IrcChannel.java @@ -142,7 +142,7 @@ public class IrcChannel extends AIrcChannel { @Override public String userModes(String nick) { - if (userModes.containsKey(nick)) + if (userModes.get(nick) != null) return Joiner.on("").join(userModes.get(nick)); else return ""; @@ -327,12 +327,14 @@ public class IrcChannel extends AIrcChannel { @Override public void _setUserModes(@NonNull QIrcUser ircuser, String modes) { - if (isKnownUser(ircuser)) { + if (!isKnownUser(ircuser)) + return; - userModes.put(ircuser.nick(), ModeUtils.toModes(modes)); - users.add(ircuser.nick()); - _update(); - } + String nick = ircuser.nick(); + userModes.put(nick, ModeUtils.toModes(modes)); + users.add(nick); + users.notifyItemChanged(nick); + _update(); } @Override @@ -345,9 +347,10 @@ public class IrcChannel extends AIrcChannel { if (!isKnownUser(ircuser) || !isValidChannelUserMode(mode)) return; - if (!userModes.get(ircuser.nick()).contains(ModeUtils.toMode(mode))) { - userModes.get(ircuser.nick()).add(ModeUtils.toMode(mode)); - users.notifyItemChanged(ircuser.nick()); + String nick = ircuser.nick(); + if (!userModes.get(nick).contains(ModeUtils.toMode(mode))) { + userModes.get(nick).add(ModeUtils.toMode(mode)); + users.notifyItemChanged(nick); _update(); } } @@ -362,8 +365,10 @@ public class IrcChannel extends AIrcChannel { if (!isKnownUser(ircuser) || !isValidChannelUserMode(mode)) return; - if (userModes.get(ircuser.nick()).contains(ModeUtils.toMode(mode))) { - userModes.get(ircuser.nick()).remove(ModeUtils.toMode(mode)); + String nick = ircuser.nick(); + if (userModes.get(nick).contains(ModeUtils.toMode(mode))) { + userModes.get(nick).remove(ModeUtils.toMode(mode)); + users.notifyItemChanged(nick); _update(); } } @@ -438,11 +443,8 @@ public class IrcChannel extends AIrcChannel { /* TODO: Use just the nick in userModes and users instead – that should make sync things a lot easier */ if (cachedUserModes != null) { for (String username : cachedUserModes.keySet()) { - QIrcUser ircUser = network().ircUser(username); - if (ircUser != null) { - userModes.put(ircUser.nick(), ModeUtils.toModes(cachedUserModes.get(username))); - users.add(ircUser.nick()); - } + userModes.put(username, ModeUtils.toModes(cachedUserModes.get(username))); + users.add(username); } } if (cachedChanModes != null) { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/IrcUser.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/IrcUser.java index 9c3840c4d..bb22de2bf 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/IrcUser.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/IrcUser.java @@ -269,12 +269,6 @@ public class IrcUser extends AIrcUser { @Override public void _setNick(String nick) { this.nick = nick; - updateObjectName(); - _update(); - } - - private void updateObjectName() { - setObjectName(getObjectName()); _update(); } @@ -284,10 +278,25 @@ public class IrcUser extends AIrcUser { return String.format(Locale.US, "%d/%s", network().networkId(), nick()); } + @Override + public void setObjectName(@Nullable String objectName) { + network().ircUserNickChanged(nick, objectName.split("/")[1]); + super.setObjectName(objectName); + } + @Override public void _setRealName(String realName) { this.realName = realName; _update(); + for (String channel : this.channels) { + QNetwork network = this.network(); + if (network != null) { + QIrcChannel channel1 = network.ircChannel(channel); + if (channel1 != null) { + channel1.users().notifyItemChanged(nick); + } + } + } } @Override @@ -481,6 +490,11 @@ public class IrcUser extends AIrcUser { public void _update(QIrcUser from) { } + @Override + public void _update() { + super._update(); + } + @NonNull @Override public String toString() { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/Network.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/Network.java index d1b2e780c..e21263dca 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/Network.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/Network.java @@ -26,6 +26,7 @@ import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -75,13 +76,11 @@ public class Network extends ANetwork implements Observer { private IrcModeProvider modeProvider; private IrcCaseMappers.IrcCaseMapper caseMapper; - public Network(Map<String, QIrcChannel> channels, - Map<String, QIrcUser> nicks, + public Network(Collection<QIrcChannel> channels, + Collection<QIrcUser> nicks, List<NetworkServer> serverList, Map<String, String> supports, int connectionState, String currentServer, boolean isConnected, int latency, String myNick, NetworkInfo networkInfo) { - this.channels = new HashMap<>(channels); - this.nicks = new HashMap<>(nicks); this.supports = new HashMap<>(supports); this.connectionState = ConnectionState.of(connectionState); this.currentServer = currentServer; @@ -92,15 +91,22 @@ public class Network extends ANetwork implements Observer { this.networkInfo._setServerList(serverList); updateCaseMapper(); + + this.channels = new HashMap<>(); + for (QIrcChannel qIrcChannel : channels) { + this.channels.put(caseMapper.toLowerCase(qIrcChannel.name()), qIrcChannel); + } + this.nicks = new HashMap<>(); + for (QIrcUser qIrcUser : nicks) { + this.nicks.put(caseMapper.toLowerCase(qIrcUser.nick()), qIrcUser); + } } - public Network(Map<String, QIrcChannel> channels, Map<String, QIrcUser> users, Map<String, String> supports, int connectionState, String currentServer, boolean isConnected, int latency, String myNick, NetworkInfo networkInfo) { + public Network(Collection<QIrcChannel> channels, Collection<QIrcUser> users, Map<String, String> supports, int connectionState, String currentServer, boolean isConnected, int latency, String myNick, NetworkInfo networkInfo) { this(channels, users, Collections.emptyList(), supports, connectionState, currentServer, isConnected, latency, myNick, networkInfo); } - public Network(Map<String, QIrcChannel> channels, Map<String, QIrcUser> nicks, List<NetworkServer> serverList, Map<String, String> supports, ConnectionState connectionState, String currentServer, boolean isConnected, int latency, String myNick, NetworkInfo networkInfo) { - this.channels = new HashMap<>(channels); - this.nicks = new HashMap<>(nicks); + public Network(Collection<QIrcChannel> channels, Collection<QIrcUser> nicks, List<NetworkServer> serverList, Map<String, String> supports, ConnectionState connectionState, String currentServer, boolean isConnected, int latency, String myNick, NetworkInfo networkInfo) { this.supports = new HashMap<>(supports); this.connectionState = connectionState; this.currentServer = currentServer; @@ -111,13 +117,22 @@ public class Network extends ANetwork implements Observer { this.networkInfo._setServerList(serverList); updateCaseMapper(); + + this.channels = new HashMap<>(); + for (QIrcChannel qIrcChannel : channels) { + this.channels.put(caseMapper.toLowerCase(qIrcChannel.name()), qIrcChannel); + } + this.nicks = new HashMap<>(); + for (QIrcUser qIrcUser : nicks) { + this.nicks.put(caseMapper.toLowerCase(qIrcUser.nick()), qIrcUser); + } } @NonNull public static QNetwork create(int network) { return new Network( - Collections.emptyMap(), - Collections.emptyMap(), + Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), Collections.emptyMap(), ConnectionState.Disconnected, @@ -197,6 +212,17 @@ public class Network extends ANetwork implements Observer { return index == -1 ? Integer.MAX_VALUE : index; } + @Override + public int lowestModeIndex(String mode) { + int lowestIndex = Integer.MAX_VALUE; + for (String m : CompatibilityUtils.partStringByChar(mode)) { + int index = modeToIndex(m); + if (index < lowestIndex) + lowestIndex = index; + } + return lowestIndex; + } + @NonNull @Override public ChannelModeType channelModeType(char mode) { @@ -418,7 +444,7 @@ public class Network extends ANetwork implements Observer { @Override public QIrcUser ircUser(String nickname) { - return nicks.get(nickname); + return nicks.get(caseMapper.toLowerCase(nickname)); } @NonNull @@ -716,15 +742,15 @@ public class Network extends ANetwork implements Observer { IrcUser user = IrcUser.create(mask); user.init(this, client); client.requestInitObject("IrcUser", user.getObjectName()); - nicks.put(user.nick(), user); + nicks.put(caseMapper.toLowerCase(user.nick()), user); _update(); return user; } @Override - public void _ircUserNickChanged(@NonNull String oldNick, @NonNull String newNick) { + public void ircUserNickChanged(@NonNull String oldNick, @NonNull String newNick) { if (!caseMapper.equalsIgnoreCase(oldNick, newNick)) { - nicks.put(newNick, nicks.remove(oldNick)); + nicks.put(caseMapper.toLowerCase(newNick), nicks.remove(caseMapper.toLowerCase(oldNick))); for (QIrcChannel channel : channels.values()) { channel._ircUserNickChanged(oldNick, newNick); } @@ -793,7 +819,7 @@ public class Network extends ANetwork implements Observer { @Override public void _addIrcChannel(@NonNull IrcChannel ircChannel) { - channels.put(ircChannel.name(), ircChannel); + channels.put(caseMapper.toLowerCase(ircChannel.name()), ircChannel); } @Override @@ -810,8 +836,11 @@ public class Network extends ANetwork implements Observer { @Override public void _update() { super._update(); - if (client != null) - client.networkManager().networks().notifyItemChanged(client.networkManager().networks().indexOf(this)); + if (client != null) { + int position = client.networkManager().networks().indexOf(this); + if (position != -1) + client.networkManager().networks().notifyItemChanged(position); + } } private void updateDisplay() { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/interfaces/QNetwork.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/interfaces/QNetwork.java index 9e4fa1af3..5174f07ce 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/interfaces/QNetwork.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/interfaces/QNetwork.java @@ -59,6 +59,8 @@ public interface QNetwork extends QObservable<QNetwork> { int modeToIndex(String mode); + int lowestModeIndex(String mode); + @NonNull ChannelModeType channelModeType(final char mode); @@ -314,11 +316,8 @@ public interface QNetwork extends QObservable<QNetwork> { QIrcUser _updateNickFromMask(final String mask); - @Synced void ircUserNickChanged(String oldNick, String newnick); - void _ircUserNickChanged(String oldNick, String newnick); - @Synced void connect(); diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/invokers/INetwork.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/invokers/INetwork.java index f5d797014..477dd548a 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/invokers/INetwork.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/invokers/INetwork.java @@ -141,9 +141,6 @@ public class INetwork implements Invoker<QNetwork> { case "setNetworkInfo": { obj._setNetworkInfo((NetworkInfo) function.params.get(0)); } break; - case "ircUserNickChanged": { - obj._ircUserNickChanged((String) function.params.get(0), (String) function.params.get(1)); - } break; case "connect": { obj._connect(); } break; diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MainActivity.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MainActivity.java index 4b46c6976..a168e595d 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MainActivity.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MainActivity.java @@ -80,10 +80,12 @@ import de.kuschku.libquassel.syncables.types.interfaces.QIrcChannel; import de.kuschku.libquassel.syncables.types.interfaces.QIrcUser; import de.kuschku.quasseldroid_ng.R; import de.kuschku.quasseldroid_ng.service.ClientBackgroundThread; +import de.kuschku.quasseldroid_ng.ui.chat.dialogs.CoreInfoDialogBuilder; import de.kuschku.quasseldroid_ng.ui.chat.drawer.ActionModeHandler; import de.kuschku.quasseldroid_ng.ui.chat.drawer.BufferViewConfigAdapter; import de.kuschku.quasseldroid_ng.ui.chat.fragment.ChatFragment; import de.kuschku.quasseldroid_ng.ui.chat.fragment.LoadingFragment; +import de.kuschku.quasseldroid_ng.ui.chat.nicklist.NickListAdapter; import de.kuschku.quasseldroid_ng.ui.chat.util.Status; import de.kuschku.quasseldroid_ng.ui.coresettings.aliases.AliasListActivity; import de.kuschku.quasseldroid_ng.ui.coresettings.chatlist.ChatListListActivity; @@ -98,6 +100,7 @@ import de.kuschku.util.annotationbind.AutoBinder; import de.kuschku.util.certificates.CertificateUtils; import de.kuschku.util.certificates.SQLiteCertificateManager; import de.kuschku.util.servicebound.BoundActivity; +import de.kuschku.util.ui.DividerItemDecoration; import de.kuschku.util.ui.MenuTint; import rx.android.schedulers.AndroidSchedulers; @@ -109,23 +112,32 @@ public class MainActivity extends BoundActivity { * This object encapsulates the current status of the activity – opened bufferview, for example */ private final Status status = new Status(); + /** * Host layout for content fragment, for example showing a loader or the chat */ @Bind(R.id.chatList) RecyclerView chatList; + + @Bind(R.id.nickList) + RecyclerView nickList; + @Bind(R.id.chatListSpinner) AppCompatSpinner chatListSpinner; + @Bind(R.id.chatListToolbar) Toolbar chatListToolbar; + @Nullable @Bind(R.id.drawer_layout) DrawerLayout drawerLayout; + /** * Main ActionBar */ @Bind(R.id.toolbar) Toolbar toolbar; + private AccountManager manager; private ToolbarWrapper toolbarWrapper; @@ -135,6 +147,8 @@ public class MainActivity extends BoundActivity { private Bundle coreSetupResult; private boolean coreSetupCancelled; + private CoreInfoDialogBuilder coreInfoDialogBuilder; + private NickListAdapter nickListAdapter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -168,6 +182,8 @@ public class MainActivity extends BoundActivity { chatList.setLayoutManager(new LinearLayoutManager(this)); chatList.setAdapter(chatListAdapter); + coreInfoDialogBuilder = new CoreInfoDialogBuilder(this); + chatListToolbar.inflateMenu(R.menu.chatlist); MenuTint.colorIcons(chatListToolbar.getMenu(), AutoBinder.obtainColor(R.attr.colorFill, chatListToolbar.getContext().getTheme())); chatListToolbar.setOnMenuItemClickListener(item -> { @@ -196,6 +212,12 @@ public class MainActivity extends BoundActivity { status.onRestoreInstanceState(savedInstanceState); manager = new AccountManager(this); + + nickListAdapter = new NickListAdapter(context); + nickList.setAdapter(nickListAdapter); + nickList.setLayoutManager(new LinearLayoutManager(this)); + nickList.setItemAnimator(new DefaultItemAnimator()); + nickList.addItemDecoration(new DividerItemDecoration(this)); } @Override @@ -290,6 +312,10 @@ public class MainActivity extends BoundActivity { case R.id.action_aliaslist: startActivity(new Intent(this, AliasListActivity.class)); return true; + case R.id.action_coreinfo: + if (context.client() != null && context.client().coreInfo() != null) + coreInfoDialogBuilder.build(manager.account(context.settings().preferenceLastAccount.get()), context.client().coreInfo(), context.client().certificateChain()).show(); + return true; default: return super.onOptionsItemSelected(item); } @@ -301,23 +327,6 @@ public class MainActivity extends BoundActivity { finish(); } - /* - - private AccountHeader buildAccountHeader() { - return new AccountHeaderBuilder() - .withActivity(this) - .withCompactStyle(true) - .withHeaderBackground(R.drawable.bg1) - .withProfileImagesVisible(false) - .withOnAccountHeaderListener((view, profile, current) -> { - selectBufferViewConfig((int) profile.getIdentifier()); - return true; - }) - .build(); - } - - */ - @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) public void onEventMainThread(ConnectionChangeEvent event) { onConnectionChange(event.status); @@ -350,6 +359,7 @@ public class MainActivity extends BoundActivity { } private void updateBuffer(int id) { + nickListAdapter.setChannel(null); Client client = context.client(); if (client != null) { Buffer buffer = client.bufferManager().buffer(id); @@ -364,6 +374,7 @@ public class MainActivity extends BoundActivity { } } else if (buffer instanceof ChannelBuffer) { QIrcChannel channel = ((ChannelBuffer) buffer).getChannel(); + nickListAdapter.setChannel(channel); if (channel == null) { toolbarWrapper.setSubtitle(null); } else { diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/dialogs/CoreInfoDialogBuilder.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/dialogs/CoreInfoDialogBuilder.java new file mode 100644 index 000000000..90d06a355 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/dialogs/CoreInfoDialogBuilder.java @@ -0,0 +1,114 @@ +/* + * QuasselDroid - Quassel client for Android + * Copyright (C) 2016 Janne Koschinski + * Copyright (C) 2016 Ken Børge Viktil + * Copyright (C) 2016 Magnus Fjell + * Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid_ng.ui.chat.dialogs; + +import android.content.Context; +import android.text.Html; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.afollestad.materialdialogs.MaterialDialog; + +import org.cryptacular.x509.dn.NameReader; +import org.cryptacular.x509.dn.StandardAttributeType; + +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Locale; + +import butterknife.Bind; +import butterknife.ButterKnife; +import de.kuschku.libquassel.syncables.types.impl.CoreInfo; +import de.kuschku.quasseldroid_ng.R; +import de.kuschku.util.accounts.Account; +import de.kuschku.util.certificates.CertificateUtils; + +public class CoreInfoDialogBuilder { + private Context context; + + public CoreInfoDialogBuilder(Context context) { + this.context = context; + } + + public MaterialDialog build(Account account, CoreInfo coreInfo, X509Certificate[] certificateChain) { + View view = LayoutInflater.from(context).inflate(R.layout.dialog_coreinfo, null); + CoreInfoViewHolder holder = new CoreInfoViewHolder(view); + holder.bind(account, coreInfo, certificateChain); + + return new MaterialDialog.Builder(context) + .customView(view, true) + .backgroundColorAttr(R.attr.colorBackgroundDialog) + .positiveColorAttr(R.attr.colorAccent) + .positiveText(R.string.actionClose) + .build(); + } + + private String issuerCN(X509Certificate certificate) { + return new NameReader(certificate).readIssuer().getValue(StandardAttributeType.CommonName); + } + + class CoreInfoViewHolder { + @Bind(R.id.address) + TextView address; + + @Bind(R.id.verified) + TextView verified; + + @Bind(R.id.fingerprint) + TextView fingerprint; + + @Bind(R.id.coreVersion) + TextView coreVersion; + + @Bind(R.id.coreBuildDate) + TextView coreBuildDate; + + @Bind(R.id.uptime) + TextView uptime; + + @Bind(R.id.connected) + TextView connected; + + public CoreInfoViewHolder(View view) { + ButterKnife.bind(this, view); + } + + public void bind(Account account, CoreInfo coreInfo, X509Certificate[] certificateChain) { + address.setText(String.format(Locale.US, "%s:%d", account.host, account.port)); + if (certificateChain != null) { + verified.setText(context.getString(R.string.labelCoreVerifier, issuerCN(certificateChain[0]))); + try { + fingerprint.setText(CertificateUtils.certificateToFingerprint(certificateChain[0])); + } catch (NoSuchAlgorithmException | CertificateEncodingException e) { + fingerprint.setVisibility(View.GONE); + } + } + coreVersion.setText(Html.fromHtml(coreInfo.quasselVersion())); + coreBuildDate.setText(coreInfo.quasselBuildDate()); + uptime.setText(context.getString(R.string.labelCoreUptimeValue, DateUtils.getRelativeTimeSpanString(context, coreInfo.startTime().getMillis()))); + connected.setText(context.getString(R.string.labelCoreConnectedCientsValue, coreInfo.sessionConnectedClients())); + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigAdapter.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigAdapter.java index 444821be6..b58eacf18 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigAdapter.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigAdapter.java @@ -161,7 +161,7 @@ public class BufferViewConfigAdapter extends ExpandableRecyclerAdapter<NetworkVi } else if (name2 == null) { return -1; } else { - return name1.compareTo(name2); + return name1.compareToIgnoreCase(name2); } } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicklist/NickListAdapter.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicklist/NickListAdapter.java new file mode 100644 index 000000000..f4f4f606f --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicklist/NickListAdapter.java @@ -0,0 +1,181 @@ +/* + * QuasselDroid - Quassel client for Android + * Copyright (C) 2016 Janne Koschinski + * Copyright (C) 2016 Ken Børge Viktil + * Copyright (C) 2016 Magnus Fjell + * Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid_ng.ui.chat.nicklist; + +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.ButterKnife; +import de.kuschku.libquassel.syncables.types.interfaces.QIrcChannel; +import de.kuschku.libquassel.syncables.types.interfaces.QIrcUser; +import de.kuschku.quasseldroid_ng.R; +import de.kuschku.quasseldroid_ng.ui.theme.AppContext; +import de.kuschku.util.CompatibilityUtils; +import de.kuschku.util.backports.Objects; +import de.kuschku.util.observables.callbacks.ElementCallback; +import de.kuschku.util.observables.callbacks.wrappers.AdapterUICallbackWrapper; +import de.kuschku.util.observables.lists.ObservableSortedList; + +public class NickListAdapter extends RecyclerView.Adapter<NickListAdapter.NickViewHolder> { + private final AppContext context; + QIrcChannel channel; + + List<QIrcUser> list = new ArrayList<>(); + ObservableSortedList<QIrcUser> users = new ObservableSortedList<>(QIrcUser.class, new ObservableSortedList.ItemComparator<QIrcUser>() { + @Override + public int compare(QIrcUser o1, QIrcUser o2) { + if (channel.userModes(o1).equals(channel.userModes(o2))) { + return o1.nick().compareToIgnoreCase(o2.nick()); + } else { + return channel.network().lowestModeIndex(channel.userModes(o1)) - channel.network().lowestModeIndex(channel.userModes(o2)); + } + } + + @Override + public boolean areContentsTheSame(QIrcUser oldItem, QIrcUser newItem) { + return Objects.equals(oldItem.userModes(), newItem.userModes()) && Objects.equals(oldItem.realName(), newItem.realName()); + } + + @Override + public boolean areItemsTheSame(QIrcUser item1, QIrcUser item2) { + return Objects.equals(item1.nick(), item2.nick()); + } + }); + private ElementCallback<String> callback = new ElementCallback<String>() { + @Override + public void notifyItemInserted(String element) { + QIrcUser qIrcUser = channel.network().ircUser(element); + users.add(qIrcUser); + list.add(users.indexOf(qIrcUser), qIrcUser); + } + + @Override + public void notifyItemRemoved(String element) { + for (int i = 0; i < users.size(); i++) { + QIrcUser user = users.get(i); + if (user.nick().equals(element)) { + users.remove(i); + list.remove(user); + } + } + } + + @Override + public void notifyItemChanged(String element) { + QIrcUser object = channel.network().ircUser(element); + if (object != null) { + users.notifyItemChanged(list.indexOf(object)); + list.remove(object); + list.add(users.indexOf(object), object); + } + } + }; + + public NickListAdapter(AppContext context) { + this.context = context; + users.addCallback(new AdapterUICallbackWrapper(this)); + } + + public void setChannel(QIrcChannel channel) { + if (this.channel != null) + this.channel.users().removeCallback(callback); + this.channel = channel; + this.users.clear(); + this.list.clear(); + if (this.channel != null) { + for (String nick : channel.users()) { + QIrcUser ircUser = channel.network().ircUser(nick); + if (ircUser != null) { + users.add(ircUser); + list.add(users.indexOf(ircUser), ircUser); + } + } + this.channel.users().addCallback(callback); + } + notifyDataSetChanged(); + } + + @Override + public NickViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.widget_nick, parent, false); + return new NickViewHolder(view); + } + + @Override + public void onBindViewHolder(NickViewHolder holder, int position) { + holder.bind(users.get(position)); + } + + @Override + public int getItemCount() { + return users.size(); + } + + @NonNull + public String getPrefixedFromMode(String text) { + StringBuilder builder = new StringBuilder(); + for (String s : CompatibilityUtils.partStringByChar(text)) { + builder.append(channel.network().modeToPrefix(s)); + } + return builder.toString(); + } + + class NickViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.mode) + TextView mode; + + @Bind(R.id.nick) + TextView nick; + + @Bind(R.id.realname) + TextView realname; + + public NickViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + public void bind(QIrcUser qIrcUser) { + nick.setText(qIrcUser.nick()); + realname.setText(qIrcUser.realName()); + String text = channel.userModes(qIrcUser); + if (text.isEmpty()) { + mode.setVisibility(View.INVISIBLE); + mode.setText(null); + } else { + mode.setVisibility(View.VISIBLE); + String prefixes = getPrefixedFromMode(text); + mode.setBackground(context.themeUtil().res.badge(channel.network().lowestModeIndex(text))); + mode.setText(prefixes); + } + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/network/AllNetworksItem.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/network/AllNetworksItem.java index c8fca222c..db4e40448 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/network/AllNetworksItem.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/network/AllNetworksItem.java @@ -105,6 +105,11 @@ class AllNetworksItem implements QNetwork { return 0; } + @Override + public int lowestModeIndex(String mode) { + return 0; + } + @NonNull @Override public ChannelModeType channelModeType(char mode) { @@ -662,11 +667,6 @@ class AllNetworksItem implements QNetwork { } - @Override - public void _ircUserNickChanged(String oldNick, String newnick) { - - } - @Override public void connect() { diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java index 11c2f7910..f9f801aae 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java @@ -28,8 +28,10 @@ import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.v4.content.res.ResourcesCompat; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.view.ContextThemeWrapper; +import android.util.SparseArray; import android.util.SparseIntArray; import de.kuschku.libquassel.events.ConnectionChangeEvent; @@ -84,6 +86,7 @@ public class ThemeUtil { try { res.colors = null; AutoBinder.bind(res, wrapper); + res.bind(wrapper); AutoBinder.bind(translations, wrapper); AutoBinder.bind(chanModes, wrapper); } catch (IllegalAccessException e) { @@ -935,16 +938,32 @@ public class ThemeUtil { public int actionBarSize; private SparseIntArray colors; + private SparseArray<Drawable> badges; public int colorToId(int foregroundColor) { + return colors.get(foregroundColor, -1); + } + + public Drawable badge(int sendercolor) { + return badges.get(sendercolor, null); + } + + public void bind(ContextThemeWrapper wrapper) { + if (badges == null) { + badges = new SparseArray<>(16); + for (int i = 0; i < senderColors.length; i++) { + Drawable drawable = ResourcesCompat.getDrawable(wrapper.getResources(), R.drawable.badge, wrapper.getTheme()); + DrawableCompat.setTint(drawable, senderColors[i]); + badges.put(i, drawable); + } + } + if (colors == null) { colors = new SparseIntArray(16); for (int i = 0; i < mircColors.length; i++) { colors.put(mircColors[i], i); } } - - return colors.get(foregroundColor, -1); } } } diff --git a/app/src/main/java/de/kuschku/util/CompatibilityUtils.java b/app/src/main/java/de/kuschku/util/CompatibilityUtils.java index f5eacb925..ecb049d45 100644 --- a/app/src/main/java/de/kuschku/util/CompatibilityUtils.java +++ b/app/src/main/java/de/kuschku/util/CompatibilityUtils.java @@ -96,8 +96,9 @@ public class CompatibilityUtils { @NonNull public static String[] partStringByChar(@NonNull String str) { String[] chars = new String[str.length()]; + char[] charArray = str.toCharArray(); for (int i = 0; i < chars.length; i++) { - chars[i] = str.substring(i, i + 1); + chars[i] = new String(charArray, i, 1); } return chars; } diff --git a/app/src/main/java/de/kuschku/util/niohelpers/WrappedChannel.java b/app/src/main/java/de/kuschku/util/niohelpers/WrappedChannel.java index 829c65f8d..7f73372ce 100644 --- a/app/src/main/java/de/kuschku/util/niohelpers/WrappedChannel.java +++ b/app/src/main/java/de/kuschku/util/niohelpers/WrappedChannel.java @@ -36,6 +36,7 @@ import java.nio.channels.ByteChannel; import java.nio.channels.ClosedChannelException; import java.nio.channels.InterruptibleChannel; import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; @@ -48,6 +49,7 @@ import de.kuschku.libquassel.ssl.CertificateManager; import de.kuschku.libquassel.ssl.QuasselTrustManager; import de.kuschku.util.CompatibilityUtils; import de.kuschku.util.accounts.ServerAddress; +import de.kuschku.util.backports.Consumer; public class WrappedChannel implements Flushable, ByteChannel, InterruptibleChannel { @Nullable @@ -95,9 +97,10 @@ public class WrappedChannel implements Flushable, ByteChannel, InterruptibleChan public static WrappedChannel withSSL(@NonNull WrappedChannel channel, @NonNull CertificateManager certificateManager, - @NonNull ServerAddress address) throws GeneralSecurityException, IOException { + @NonNull ServerAddress address, + @NonNull Consumer<X509Certificate[]> callback) throws GeneralSecurityException, IOException { SSLContext context = SSLContext.getInstance("TLSv1.2"); - TrustManager[] managers = new TrustManager[]{QuasselTrustManager.fromDefault(certificateManager, address)}; + TrustManager[] managers = new TrustManager[]{QuasselTrustManager.fromDefault(certificateManager, address, callback)}; context.init(null, managers, null); SSLSocketFactory factory = context.getSocketFactory(); SSLSocket socket = (SSLSocket) factory.createSocket(channel.socket, address.host, address.port, true); 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 eb8155929..68ad2a8f3 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 @@ -246,7 +246,9 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> { } public void notifyItemChanged(int position) { - callback.notifyItemChanged(position); + T obj = get(position); + list.recalculatePositionOfItemAt(position); + callback.notifyItemChanged(indexOf(obj)); } @NonNull diff --git a/app/src/main/res/drawable/badge.xml b/app/src/main/res/drawable/badge.xml index 8d3a3640f..de1160f36 100644 --- a/app/src/main/res/drawable/badge.xml +++ b/app/src/main/res/drawable/badge.xml @@ -20,7 +20,7 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="@color/md_red_500"/> + <solid/> <padding android:bottom="10dp" android:left="10dp" diff --git a/app/src/main/res/layout-w720dp/activity_main.xml b/app/src/main/res/layout-w720dp/activity_main.xml deleted file mode 100644 index 8c95f4d18..000000000 --- a/app/src/main/res/layout-w720dp/activity_main.xml +++ /dev/null @@ -1,93 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ QuasselDroid - Quassel client for Android - ~ Copyright (C) 2016 Janne Koschinski - ~ Copyright (C) 2016 Ken Børge Viktil - ~ Copyright (C) 2016 Magnus Fjell - ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> - ~ - ~ This program is free software: you can redistribute it and/or modify it - ~ under the terms of the GNU General Public License as published by the Free - ~ Software Foundation, either version 3 of the License, or (at your option) - ~ any later version. - ~ - ~ This program is distributed in the hope that it will be useful, - ~ but WITHOUT ANY WARRANTY; without even the implied warranty of - ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - ~ GNU General Public License for more details. - ~ - ~ You should have received a copy of the GNU General Public License along - ~ with this program. If not, see <http://www.gnu.org/licenses/>. - --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal" - tools:context=".ui.chat.MainActivity"> - - <LinearLayout - android:layout_width="300dp" - android:layout_height="match_parent" - android:orientation="vertical"> - - <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" - android:theme="?attr/actionBarTheme"> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <android.support.v7.widget.Toolbar - android:id="@+id/chatListToolbar" - android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" - android:background="?attr/colorPrimary" - app:popupTheme="@style/AppTheme.PopupOverlay"> - - <android.support.v7.widget.AppCompatSpinner - android:id="@+id/chatListSpinner" - android:layout_width="fill_parent" - android:layout_height="match_parent" - app:popupTheme="@style/AppTheme.PopupOverlay"/> - - </android.support.v7.widget.Toolbar> - - <ViewStub - android:id="@+id/cab_stub" - android:layout_width="match_parent" - android:layout_height="?actionBarSize"/> - - </FrameLayout> - - </android.support.design.widget.AppBarLayout> - - <android.support.v7.widget.RecyclerView - android:id="@+id/chatList" - android:layout_width="match_parent" - android:layout_height="match_parent"/> - - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <include layout="@layout/widget_actionbar"/> - - <FrameLayout - android:id="@+id/content_host" - android:layout_width="match_parent" - android:layout_height="match_parent"/> - - </LinearLayout> - -</LinearLayout> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 867045ed9..3a8dbc9c0 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -43,6 +43,18 @@ </LinearLayout> + <android.support.design.widget.NavigationView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="end"> + + <android.support.v7.widget.RecyclerView + android:id="@+id/nickList" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + </android.support.design.widget.NavigationView> + <android.support.design.widget.NavigationView android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/app/src/main/res/layout/dialog_coreinfo.xml b/app/src/main/res/layout/dialog_coreinfo.xml new file mode 100644 index 000000000..e4dcc51e2 --- /dev/null +++ b/app/src/main/res/layout/dialog_coreinfo.xml @@ -0,0 +1,240 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ QuasselDroid - Quassel client for Android + ~ Copyright (C) 2016 Janne Koschinski + ~ Copyright (C) 2016 Ken Børge Viktil + ~ Copyright (C) 2016 Magnus Fjell + ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> + ~ + ~ This program is free software: you can redistribute it and/or modify it + ~ under the terms of the GNU General Public License as published by the Free + ~ Software Foundation, either version 3 of the License, or (at your option) + ~ any later version. + ~ + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along + ~ with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="16dp" + android:paddingTop="16dp"> + + <android.support.v7.widget.CardView + 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" + android:layout_margin="4dp" + app:cardBackgroundColor="?colorBackgroundCard" + app:cardUseCompatPadding="true" + app:contentPaddingBottom="16dp" + app:contentPaddingLeft="16dp" + app:contentPaddingRight="16dp" + app:contentPaddingTop="16dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/labelCoreAddress" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" + android:textColor="?colorForeground"/> + + <TextView + android:id="@+id/address" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" + android:textColor="?colorForeground" + tools:text="example.com:4242"/> + + </LinearLayout> + + </android.support.v7.widget.CardView> + + <android.support.v7.widget.CardView + 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" + android:layout_margin="4dp" + app:cardBackgroundColor="?colorBackgroundCard" + app:cardUseCompatPadding="true" + app:contentPaddingBottom="16dp" + app:contentPaddingLeft="16dp" + app:contentPaddingRight="16dp" + app:contentPaddingTop="16dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/labelCoreCertificate" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" + android:textColor="?colorForeground"/> + + <TextView + android:id="@+id/verified" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" + android:textColor="?colorForeground" + tools:text="The connection is verified by Let's Encrypt"/> + + <TextView + android:id="@+id/fingerprint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Small" + android:textColor="?colorForegroundSecondary" + tools:text="25:EF:C0:88:42:96:6C:66:2C:72:01:0E:2B:61:84:B2:D8:9A:AB:4D"/> + + </LinearLayout> + + </android.support.v7.widget.CardView> + + <android.support.v7.widget.CardView + 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" + android:layout_margin="4dp" + app:cardBackgroundColor="?colorBackgroundCard" + app:cardUseCompatPadding="true" + app:contentPaddingBottom="16dp" + app:contentPaddingLeft="16dp" + app:contentPaddingRight="16dp" + app:contentPaddingTop="16dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/labelCoreVersion" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" + android:textColor="?colorForeground"/> + + <TextView + android:id="@+id/coreVersion" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" + android:textColor="?colorForeground" + tools:text="v0.13-pre (0.12.0+245 git-d6129e6)"/> + + <TextView + android:id="@+id/coreBuildDate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Small" + android:textColor="?colorForegroundSecondary" + tools:text="Wed Sep 7 20:52:53 2016"/> + + </LinearLayout> + + </android.support.v7.widget.CardView> + + <android.support.v7.widget.CardView + 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" + android:layout_margin="4dp" + app:cardBackgroundColor="?colorBackgroundCard" + app:cardUseCompatPadding="true" + app:contentPaddingBottom="16dp" + app:contentPaddingLeft="16dp" + app:contentPaddingRight="16dp" + app:contentPaddingTop="16dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/labelCoreUptime" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" + android:textColor="?colorForeground"/> + + <TextView + android:id="@+id/uptime" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" + android:textColor="?colorForeground" + tools:text="The core has been last restarted on 07.09.2016 19:23:31"/> + + </LinearLayout> + + </android.support.v7.widget.CardView> + + <android.support.v7.widget.CardView + 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" + android:layout_margin="4dp" + app:cardBackgroundColor="?colorBackgroundCard" + app:cardUseCompatPadding="true" + app:contentPaddingBottom="16dp" + app:contentPaddingLeft="16dp" + app:contentPaddingRight="16dp" + app:contentPaddingTop="16dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/labelCoreConnectedClients" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" + android:textColor="?colorForeground"/> + + <TextView + android:id="@+id/connected" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" + android:textColor="?colorForeground" + tools:text="At the moment, 2 clients are connected under your account."/> + + </LinearLayout> + + </android.support.v7.widget.CardView> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/widget_actionbar.xml b/app/src/main/res/layout/widget_actionbar.xml index f8f3e6b0d..4ab110165 100644 --- a/app/src/main/res/layout/widget_actionbar.xml +++ b/app/src/main/res/layout/widget_actionbar.xml @@ -34,17 +34,17 @@ android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/toolbar_action_area" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="?attr/selectableItemBackgroundBorderless" - android:clickable="true" - android:gravity="center_vertical|start" - android:minHeight="?attr/actionBarSize" - android:orientation="vertical" - android:paddingLeft="@dimen/action_bar_default_padding_start_material" - android:paddingStart="@dimen/action_bar_default_padding_start_material"> + <LinearLayout + android:id="@+id/toolbar_action_area" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="?attr/selectableItemBackgroundBorderless" + android:clickable="true" + android:gravity="center_vertical|start" + android:minHeight="?attr/actionBarSize" + android:orientation="vertical" + android:paddingLeft="@dimen/action_bar_default_padding_start_material" + android:paddingStart="@dimen/action_bar_default_padding_start_material"> <LinearLayout android:layout_width="wrap_content" diff --git a/app/src/main/res/layout/widget_channel_mode.xml b/app/src/main/res/layout/widget_channel_mode.xml index 672ddc397..405cd25ef 100644 --- a/app/src/main/res/layout/widget_channel_mode.xml +++ b/app/src/main/res/layout/widget_channel_mode.xml @@ -19,17 +19,18 @@ ~ with this program. If not, see <http://www.gnu.org/licenses/>. --> -<android.support.v7.widget.CardView 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" - android:layout_margin="4dp" - app:cardBackgroundColor="?attr/colorBackgroundCard" - app:cardUseCompatPadding="true" - app:contentPaddingBottom="16dp" - app:contentPaddingLeft="16dp" - app:contentPaddingRight="16dp" - app:contentPaddingTop="16dp"> +<android.support.v7.widget.CardView + 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" + android:layout_margin="4dp" + app:cardBackgroundColor="?attr/colorBackgroundCard" + app:cardUseCompatPadding="true" + app:contentPaddingBottom="16dp" + app:contentPaddingLeft="16dp" + app:contentPaddingRight="16dp" + app:contentPaddingTop="16dp"> <LinearLayout android:layout_width="match_parent" diff --git a/app/src/main/res/layout/widget_nick.xml b/app/src/main/res/layout/widget_nick.xml new file mode 100644 index 000000000..d3fd352a9 --- /dev/null +++ b/app/src/main/res/layout/widget_nick.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ QuasselDroid - Quassel client for Android + ~ Copyright (C) 2016 Janne Koschinski + ~ Copyright (C) 2016 Ken Børge Viktil + ~ Copyright (C) 2016 Magnus Fjell + ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> + ~ + ~ This program is free software: you can redistribute it and/or modify it + ~ under the terms of the GNU General Public License as published by the Free + ~ Software Foundation, either version 3 of the License, or (at your option) + ~ any later version. + ~ + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along + ~ with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="56dp" + android:background="?selectableItemBackground" + android:clickable="true" + android:orientation="horizontal" + android:paddingLeft="?listPreferredItemPaddingLeft" + android:paddingRight="?listPreferredItemPaddingRight"> + + <TextView + android:id="@+id/mode" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_gravity="center_vertical" + android:background="@drawable/badge" + android:gravity="center" + android:textColor="?colorFill" + tools:text="\@" + /> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_weight="1" + android:gravity="center_vertical|start" + android:orientation="vertical"> + + <TextView + android:id="@+id/nick" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:gravity="center_vertical|start" + android:lines="1" + android:singleLine="true" + android:textSize="@dimen/material_drawer_item_primary_text" + tools:text="justJanne"/> + + <TextView + android:id="@+id/realname" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif" + android:gravity="center_vertical|start" + android:lines="1" + android:singleLine="true" + android:textSize="@dimen/material_drawer_item_primary_description" + tools:text="Janne Koschinski: https://kuschku.de/"/> + </LinearLayout> +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/widget_settings_alias.xml b/app/src/main/res/layout/widget_settings_alias.xml index 5a2706480..61adbd59b 100644 --- a/app/src/main/res/layout/widget_settings_alias.xml +++ b/app/src/main/res/layout/widget_settings_alias.xml @@ -32,7 +32,7 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_vertical" + android:layout_gravity="center_vertical" android:orientation="vertical"> <TextView diff --git a/app/src/main/res/menu/chat.xml b/app/src/main/res/menu/chat.xml index 3fd95e90d..85f7aca9c 100644 --- a/app/src/main/res/menu/chat.xml +++ b/app/src/main/res/menu/chat.xml @@ -51,6 +51,10 @@ android:id="@+id/action_aliaslist" android:title="@string/actionAlias" app:showAsAction="never"/> + <item + android:id="@+id/action_coreinfo" + android:title="@string/actionCore" + app:showAsAction="never"/> <item android:id="@+id/action_reauth" android:title="@string/actionDisconnect" diff --git a/app/src/main/res/values/strings_actions.xml b/app/src/main/res/values/strings_actions.xml index b00ff6fe2..ab5ab0d3b 100644 --- a/app/src/main/res/values/strings_actions.xml +++ b/app/src/main/res/values/strings_actions.xml @@ -25,6 +25,8 @@ <string name="actionAddAccount">Add Account</string> <string name="actionAlias">Aliases</string> <string name="actionCancel">Cancel</string> + <string name="actionClose">Close</string> + <string name="actionCore">About Core…</string> <string name="actionConnect">Connect</string> <string name="actionDelete">Delete</string> <string name="actionDisconnect">Disconnect</string> diff --git a/app/src/main/res/values/strings_coresettings.xml b/app/src/main/res/values/strings_coresettings.xml index c61ad23b7..6797493ad 100644 --- a/app/src/main/res/values/strings_coresettings.xml +++ b/app/src/main/res/values/strings_coresettings.xml @@ -102,4 +102,13 @@ <string name="labelNetworkServerProxyDefault">Default Proxy</string> <string name="labelNetworkServerProxySocks5">Socks5</string> <string name="labelNetworkServerProxyHttp">Http</string> + + <string name="labelCoreUptimeValue">This core has been online since %1$s</string> + <string name="labelCoreUptime">Uptime</string> + <string name="labelCoreConnectedClients">Connected Clients</string> + <string name="labelCoreConnectedCientsValue">At the moment, %1d clients are logged in as you.</string> + <string name="labelCoreVersion">Core Version</string> + <string name="labelCoreCertificate">Certificate</string> + <string name="labelCoreAddress">Address</string> + <string name="labelCoreVerifier">The connection is verified by %1$s</string> </resources> \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 7c1e417cc..f645b9635 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -128,6 +128,27 @@ <item name="colorAway">@color/colorAwayDark</item> </style> + <style name="AppTheme.Light.Dialog" parent="MaterialBaseTheme.Light.AlertDialog"> + <item name="colorForeground">#DE000000</item> + <item name="colorForegroundHighlight">#DE000000</item> + <item name="colorForegroundSecondary">#8A000000</item> + <item name="colorForegroundAction">#1a237e</item> + <item name="colorForegroundError">#800000</item> + + <item name="colorForegroundMirc">0x1</item> + + <item name="colorBackground">#FAFAFA</item> + <item name="android:windowBackground">@color/quasselLight_background</item> + <item name="colorBackgroundHighlight">#ff8811</item> + <item name="colorBackgroundSecondary">@null</item> + <item name="colorBackgroundCard">#FFFFFF</item> + <item name="colorBackgroundDialog">#FAFAFA</item> + + <item name="colorTintActivity">#88cc33</item> + <item name="colorTintMessage">#2277dd</item> + <item name="colorTintHighlight">#ff8811</item> + </style> + <style name="AppTheme.AppBarOverlay.Light" parent="ThemeOverlay.AppCompat.ActionBar"> <item name="colorControlNormal">@color/colorFillLight</item> <item name="android:textColorPrimary">@color/colorFillLight</item> -- GitLab