diff options
17 files changed, 402 insertions, 150 deletions
diff --git a/src/main/java/de/pixart/messenger/entities/Room.java b/src/main/java/de/pixart/messenger/entities/Room.java new file mode 100644 index 000000000..d41cbe2f2 --- /dev/null +++ b/src/main/java/de/pixart/messenger/entities/Room.java @@ -0,0 +1,88 @@ +package de.pixart.messenger.entities; + +import com.google.common.base.Objects; +import com.google.common.base.Strings; +import com.google.common.collect.ComparisonChain; + +import de.pixart.messenger.services.AvatarService; +import de.pixart.messenger.utils.LanguageUtils; +import de.pixart.messenger.utils.UIHelper; +import rocks.xmpp.addr.Jid; + +public class Room implements AvatarService.Avatarable, Comparable<Room> { + + public String address; + public String name; + public String description; + public String language; + public int nusers; + + public Room(String address, String name, String description, String language, int nusers) { + this.address = address; + this.name = name; + this.description = description; + this.language = language; + this.nusers = nusers; + } + + public Room() { + + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Jid getRoom() { + try { + return Jid.of(address); + } catch (IllegalArgumentException e) { + return null; + } + } + + public String getLanguage() { + return LanguageUtils.convert(language); + } + + @Override + public int getAvatarBackgroundColor() { + Jid room = getRoom(); + return UIHelper.getColorForName(room != null ? room.asBareJid().toEscapedString() : name); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Room room = (Room) o; + return Objects.equal(address, room.address) && + Objects.equal(name, room.name) && + Objects.equal(description, room.description); + } + + @Override + public int hashCode() { + return Objects.hashCode(address, name, description); + } + + + public boolean contains(String needle) { + return Strings.nullToEmpty(name).contains(needle) + || Strings.nullToEmpty(description).contains(needle) + || Strings.nullToEmpty(address).contains(needle); + } + + @Override + public int compareTo(Room o) { + return ComparisonChain.start() + .compare(o.nusers, nusers) + .compare(Strings.nullToEmpty(name), Strings.nullToEmpty(o.name)) + .compare(Strings.nullToEmpty(address), Strings.nullToEmpty(o.address)) + .result(); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/generator/IqGenerator.java b/src/main/java/de/pixart/messenger/generator/IqGenerator.java index f7e94b0c6..34a4e3408 100644 --- a/src/main/java/de/pixart/messenger/generator/IqGenerator.java +++ b/src/main/java/de/pixart/messenger/generator/IqGenerator.java @@ -577,4 +577,18 @@ public class IqGenerator extends AbstractGenerator { } return packet; } + + public IqPacket queryDiscoItems(Jid jid) { + IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + packet.setTo(jid); + packet.addChild("query", Namespace.DISCO_ITEMS); + return packet; + } + + public IqPacket queryDiscoInfo(Jid jid) { + IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + packet.setTo(jid); + packet.addChild("query", Namespace.DISCO_INFO); + return packet; + } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/http/services/MuclumbusService.java b/src/main/java/de/pixart/messenger/http/services/MuclumbusService.java index 6ea1adad5..fb8a28658 100644 --- a/src/main/java/de/pixart/messenger/http/services/MuclumbusService.java +++ b/src/main/java/de/pixart/messenger/http/services/MuclumbusService.java @@ -1,20 +1,15 @@ package de.pixart.messenger.http.services; -import com.google.common.base.Objects; - import java.util.Collections; import java.util.List; import java.util.Set; -import de.pixart.messenger.services.AvatarService; -import de.pixart.messenger.utils.LanguageUtils; -import de.pixart.messenger.utils.UIHelper; +import de.pixart.messenger.entities.Room; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Query; -import rocks.xmpp.addr.Jid; public interface MuclumbusService { @@ -31,55 +26,6 @@ public interface MuclumbusService { public List<Room> items; } - class Room implements AvatarService.Avatarable { - - public String address; - public String name; - public String description; - public String language; - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public Jid getRoom() { - try { - return Jid.of(address); - } catch (IllegalArgumentException e) { - return null; - } - } - - public String getLanguage() { - return LanguageUtils.convert(language); - } - - @Override - public int getAvatarBackgroundColor() { - Jid room = getRoom(); - return UIHelper.getColorForName(room != null ? room.asBareJid().toEscapedString() : name); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Room room = (Room) o; - return Objects.equal(address, room.address) && - Objects.equal(name, room.name) && - Objects.equal(description, room.description); - } - - @Override - public int hashCode() { - return Objects.hashCode(address, name, description); - } - } - class SearchRequest { public final Set<String> keywords; diff --git a/src/main/java/de/pixart/messenger/parser/IqParser.java b/src/main/java/de/pixart/messenger/parser/IqParser.java index d689d0667..be49eb431 100644 --- a/src/main/java/de/pixart/messenger/parser/IqParser.java +++ b/src/main/java/de/pixart/messenger/parser/IqParser.java @@ -1,10 +1,12 @@ package de.pixart.messenger.parser; -import androidx.annotation.NonNull; +import android.text.TextUtils; import android.util.Base64; import android.util.Log; import android.util.Pair; +import androidx.annotation.NonNull; + import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.ECPublicKey; @@ -27,12 +29,14 @@ import de.pixart.messenger.crypto.axolotl.AxolotlService; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Contact; import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.entities.Room; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.utils.Namespace; import de.pixart.messenger.xml.Element; import de.pixart.messenger.xmpp.InvalidJid; import de.pixart.messenger.xmpp.OnIqPacketReceived; import de.pixart.messenger.xmpp.OnUpdateBlocklist; +import de.pixart.messenger.xmpp.forms.Data; import de.pixart.messenger.xmpp.stanzas.IqPacket; import rocks.xmpp.addr.Jid; @@ -416,4 +420,55 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } } } + + public static List<Jid> items(IqPacket packet) { + ArrayList<Jid> items = new ArrayList<>(); + final Element query = packet.findChild("query", Namespace.DISCO_ITEMS); + if (query == null) { + return items; + } + for (Element child : query.getChildren()) { + if ("item".equals(child.getName())) { + Jid jid = child.getAttributeAsJid("jid"); + if (jid != null) { + items.add(jid); + } + } + } + return items; + } + + public static Room parseRoom(IqPacket packet) { + final Element query = packet.findChild("query", Namespace.DISCO_INFO); + if (query == null) { + return null; + } + final Element x = query.findChild("x"); + if (x == null) { + return null; + } + final Element identity = query.findChild("identity"); + Data data = Data.parse(x); + String address = packet.getFrom().toEscapedString(); + String name = identity == null ? null : identity.getAttribute("name"); + String roomName = data.getValue("muc#roomconfig_roomname"); + ; + String description = data.getValue("muc#roominfo_description"); + String language = data.getValue("muc#roominfo_lang"); + String occupants = data.getValue("muc#roominfo_occupants"); + int nusers; + try { + nusers = occupants == null ? 0 : Integer.parseInt(occupants); + } catch (NumberFormatException e) { + nusers = 0; + } + + return new Room( + address, + TextUtils.isEmpty(roomName) ? name : roomName, + description, + language, + nusers + ); + } } diff --git a/src/main/java/de/pixart/messenger/services/AvatarService.java b/src/main/java/de/pixart/messenger/services/AvatarService.java index 469de2599..0ae6f7099 100644 --- a/src/main/java/de/pixart/messenger/services/AvatarService.java +++ b/src/main/java/de/pixart/messenger/services/AvatarService.java @@ -13,14 +13,15 @@ import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; -import androidx.annotation.ColorInt; -import androidx.annotation.Nullable; -import androidx.core.content.res.ResourcesCompat; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.LruCache; +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; +import androidx.core.content.res.ResourcesCompat; + import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -39,7 +40,7 @@ import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.entities.Message; import de.pixart.messenger.entities.MucOptions; import de.pixart.messenger.entities.RawBlockable; -import de.pixart.messenger.http.services.MuclumbusService; +import de.pixart.messenger.entities.Room; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.OnAdvancedStreamFeaturesLoaded; import de.pixart.messenger.xmpp.XmppConnection; @@ -84,13 +85,13 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { return get((ListItem) avatarable, size, cachedOnly); } else if (avatarable instanceof MucOptions.User) { return get((MucOptions.User) avatarable, size, cachedOnly); - } else if (avatarable instanceof MuclumbusService.Room) { - return get((MuclumbusService.Room) avatarable, size, cachedOnly); + } else if (avatarable instanceof Room) { + return get((Room) avatarable, size, cachedOnly); } throw new AssertionError("AvatarService does not know how to generate avatar from " + avatarable.getClass().getName()); } - private Bitmap get(final MuclumbusService.Room result, final int size, boolean cacheOnly) { + private Bitmap get(final Room result, final int size, boolean cacheOnly) { final Jid room = result.getRoom(); Conversation conversation = room != null ? mXmppConnectionService.findFirstMuc(room) : null; if (conversation != null) { diff --git a/src/main/java/de/pixart/messenger/services/ChannelDiscoveryService.java b/src/main/java/de/pixart/messenger/services/ChannelDiscoveryService.java index bd92c886d..81668cb92 100644 --- a/src/main/java/de/pixart/messenger/services/ChannelDiscoveryService.java +++ b/src/main/java/de/pixart/messenger/services/ChannelDiscoveryService.java @@ -1,39 +1,48 @@ package de.pixart.messenger.services; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import org.jetbrains.annotations.NotNull; - import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import de.pixart.messenger.Config; +import de.pixart.messenger.entities.Account; +import de.pixart.messenger.entities.Room; import de.pixart.messenger.http.HttpConnectionManager; import de.pixart.messenger.http.services.MuclumbusService; -import okhttp3.Interceptor; +import de.pixart.messenger.parser.IqParser; +import de.pixart.messenger.xmpp.OnIqPacketReceived; +import de.pixart.messenger.xmpp.XmppConnection; +import de.pixart.messenger.xmpp.stanzas.IqPacket; import okhttp3.OkHttpClient; -import okhttp3.Request; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; +import rocks.xmpp.addr.Jid; public class ChannelDiscoveryService { private final XmppConnectionService service; + private MuclumbusService muclumbusService; - private final Cache<String, List<MuclumbusService.Room>> cache; + private final Cache<String, List<Room>> cache; ChannelDiscoveryService(XmppConnectionService service) { this.service = service; @@ -42,6 +51,7 @@ public class ChannelDiscoveryService { void initializeMuclumbusService() { final OkHttpClient.Builder builder = new OkHttpClient.Builder(); + if (service.useTorToConnect()) { try { builder.proxy(HttpConnectionManager.getProxy()); @@ -49,11 +59,6 @@ public class ChannelDiscoveryService { throw new RuntimeException("Unable to use Tor proxy", e); } } - try { - builder.networkInterceptors().add(new UserAgentInterceptor(service.getIqGenerator().getUserAgent())); - } catch (Exception e) { - e.printStackTrace(); - } Retrofit retrofit = new Retrofit.Builder() .client(builder.build()) .baseUrl(Config.CHANNEL_DISCOVERY) @@ -63,21 +68,28 @@ public class ChannelDiscoveryService { this.muclumbusService = retrofit.create(MuclumbusService.class); } - void discover(String query, OnChannelSearchResultsFound onChannelSearchResultsFound) { - final boolean all = query == null || query.trim().isEmpty(); - List<MuclumbusService.Room> result = cache.getIfPresent(all ? "" : query); + void cleanCache() { + cache.invalidateAll(); + } + + void discover(@NonNull final String query, Method method, OnChannelSearchResultsFound onChannelSearchResultsFound) { + List<Room> result = cache.getIfPresent(key(method, query)); if (result != null) { onChannelSearchResultsFound.onChannelSearchResultsFound(result); return; } - if (all) { - discoverChannels(onChannelSearchResultsFound); + if (method == Method.LOCAL_SERVER) { + discoverChannelsLocalServers(query, onChannelSearchResultsFound); } else { - discoverChannels(query, onChannelSearchResultsFound); + if (query.isEmpty()) { + discoverChannelsJabberNetwork(onChannelSearchResultsFound); + } else { + discoverChannelsJabberNetwork(query, onChannelSearchResultsFound); + } } } - private void discoverChannels(OnChannelSearchResultsFound listener) { + private void discoverChannelsJabberNetwork(OnChannelSearchResultsFound listener) { Call<MuclumbusService.Rooms> call = muclumbusService.getRooms(1); try { call.enqueue(new Callback<MuclumbusService.Rooms>() { @@ -89,7 +101,7 @@ public class ChannelDiscoveryService { logError(response); return; } - cache.put("", body.items); + cache.put(key(Method.JABBER_NETWORK, ""), body.items); listener.onChannelSearchResultsFound(body.items); } @@ -104,9 +116,10 @@ public class ChannelDiscoveryService { } } - private void discoverChannels(final String query, OnChannelSearchResultsFound listener) { + private void discoverChannelsJabberNetwork(final String query, OnChannelSearchResultsFound listener) { MuclumbusService.SearchRequest searchRequest = new MuclumbusService.SearchRequest(query); Call<MuclumbusService.SearchResult> searchResultCall = muclumbusService.search(searchRequest); + searchResultCall.enqueue(new Callback<MuclumbusService.SearchResult>() { @Override public void onResponse(@NonNull Call<MuclumbusService.SearchResult> call, @NonNull Response<MuclumbusService.SearchResult> response) { @@ -116,7 +129,7 @@ public class ChannelDiscoveryService { logError(response); return; } - cache.put(query, body.result.items); + cache.put(key(Method.JABBER_NETWORK, query), body.result.items); listener.onChannelSearchResultsFound(body.result.items); } @@ -128,6 +141,102 @@ public class ChannelDiscoveryService { }); } + private void discoverChannelsLocalServers(final String query, final OnChannelSearchResultsFound listener) { + final Map<Jid, Account> localMucService = getLocalMucServices(); + Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services"); + if (localMucService.size() == 0) { + listener.onChannelSearchResultsFound(Collections.emptyList()); + return; + } + if (!query.isEmpty()) { + final List<Room> cached = cache.getIfPresent(key(Method.LOCAL_SERVER, "")); + if (cached != null) { + final List<Room> results = copyMatching(cached, query); + cache.put(key(Method.LOCAL_SERVER, query), results); + listener.onChannelSearchResultsFound(results); + } + } + final AtomicInteger queriesInFlight = new AtomicInteger(); + final List<Room> rooms = new ArrayList<>(); + for (Map.Entry<Jid, Account> entry : localMucService.entrySet()) { + IqPacket itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey()); + queriesInFlight.incrementAndGet(); + service.sendIqPacket(entry.getValue(), itemsRequest, (account, itemsResponse) -> { + if (itemsResponse.getType() == IqPacket.TYPE.RESULT) { + final List<Jid> items = IqParser.items(itemsResponse); + for (Jid item : items) { + IqPacket infoRequest = service.getIqGenerator().queryDiscoInfo(item); + queriesInFlight.incrementAndGet(); + service.sendIqPacket(account, infoRequest, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket infoResponse) { + if (infoResponse.getType() == IqPacket.TYPE.RESULT) { + final Room room = IqParser.parseRoom(infoResponse); + if (room != null) { + rooms.add(room); + } + if (queriesInFlight.decrementAndGet() <= 0) { + finishDiscoSearch(rooms, query, listener); + } + } else { + queriesInFlight.decrementAndGet(); + } + } + }); + } + } + if (queriesInFlight.decrementAndGet() <= 0) { + finishDiscoSearch(rooms, query, listener); + } + }); + } + } + + private void finishDiscoSearch(List<Room> rooms, String query, OnChannelSearchResultsFound listener) { + Collections.sort(rooms); + cache.put(key(Method.LOCAL_SERVER, ""), rooms); + if (query.isEmpty()) { + listener.onChannelSearchResultsFound(rooms); + } else { + List<Room> results = copyMatching(rooms, query); + cache.put(key(Method.LOCAL_SERVER, query), results); + listener.onChannelSearchResultsFound(rooms); + } + } + + private static List<Room> copyMatching(List<Room> haystack, String needle) { + ArrayList<Room> result = new ArrayList<>(); + for (Room room : haystack) { + if (room.contains(needle)) { + result.add(room); + } + } + return result; + } + + private Map<Jid, Account> getLocalMucServices() { + final HashMap<Jid, Account> localMucServices = new HashMap<>(); + for (Account account : service.getAccounts()) { + if (account.isEnabled()) { + final XmppConnection xmppConnection = account.getXmppConnection(); + if (xmppConnection == null) { + continue; + } + for (final String mucService : xmppConnection.getMucServers()) { + Jid jid = Jid.of(mucService); + if (!localMucServices.containsKey(jid)) { + localMucServices.put(jid, account); + } + } + } + } + return localMucServices; + } + + private static String key(Method method, String query) { + return String.format("%s\00%s", method, query); + } + private static void logError(final Response response) { final ResponseBody errorBody = response.errorBody(); Log.d(Config.LOGTAG, "code from muclumbus=" + response.code()); @@ -142,24 +251,11 @@ public class ChannelDiscoveryService { } public interface OnChannelSearchResultsFound { - void onChannelSearchResultsFound(List<MuclumbusService.Room> results); + void onChannelSearchResultsFound(List<Room> results); } - private class UserAgentInterceptor implements Interceptor { - private final String userAgent; - - UserAgentInterceptor(String userAgent) { - this.userAgent = userAgent; - } - - @NotNull - @Override - public okhttp3.Response intercept(Chain chain) throws IOException { - Request originalRequest = chain.request(); - Request requestWithUserAgent = originalRequest.newBuilder() - .header("User-Agent", userAgent) - .build(); - return chain.proceed(requestWithUserAgent); - } + public enum Method { + JABBER_NETWORK, + LOCAL_SERVER } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index e7c587107..5de4c79b9 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -44,6 +44,8 @@ import androidx.annotation.IntegerRes; import androidx.core.app.RemoteInput; import androidx.core.content.ContextCompat; +import com.google.common.base.Strings; + import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionID; @@ -928,8 +930,8 @@ public class XmppConnectionService extends Service { mChannelDiscoveryService.initializeMuclumbusService(); } - public void discoverChannels(String query, ChannelDiscoveryService.OnChannelSearchResultsFound onChannelSearchResultsFound) { - mChannelDiscoveryService.discover(query, onChannelSearchResultsFound); + public void discoverChannels(String query, ChannelDiscoveryService.Method method, ChannelDiscoveryService.OnChannelSearchResultsFound onChannelSearchResultsFound) { + mChannelDiscoveryService.discover(Strings.nullToEmpty(query).trim(), method, onChannelSearchResultsFound); } public boolean isDataSaverDisabled() { @@ -2536,6 +2538,7 @@ public class XmppConnectionService extends Service { getNotificationService().updateErrorNotification(); toggleForegroundService(); syncEnabledAccountSetting(); + mChannelDiscoveryService.cleanCache(); return true; } else { return false; @@ -3379,9 +3382,7 @@ public class XmppConnectionService extends Service { } public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) { - IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.setTo(conversation.getJid().asBareJid()); - request.query("http://jabber.org/protocol/disco#info"); + IqPacket request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid()); sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -4831,7 +4832,7 @@ public class XmppConnectionService extends Service { request.setTo(jid); final String node = presence.getNode(); final String ver = presence.getVer(); - final Element query = request.query("http://jabber.org/protocol/disco#info"); + final Element query = request.query(Namespace.DISCO_INFO); if (node != null && ver != null) { query.setAttribute("node", node + "#" + ver); } diff --git a/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java b/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java index 1dee03021..77892c16e 100644 --- a/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; +import android.preference.PreferenceManager; import android.text.Html; import android.view.KeyEvent; import android.view.Menu; @@ -28,21 +29,27 @@ import de.pixart.messenger.databinding.ActivityChannelDiscoveryBinding; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Bookmark; import de.pixart.messenger.entities.Conversation; -import de.pixart.messenger.http.services.MuclumbusService; +import de.pixart.messenger.entities.Room; import de.pixart.messenger.services.ChannelDiscoveryService; import de.pixart.messenger.ui.adapter.ChannelSearchResultAdapter; import de.pixart.messenger.ui.util.PendingItem; import de.pixart.messenger.ui.util.SoftKeyboardUtils; +import de.pixart.messenger.ui.util.StyledAttributes; import de.pixart.messenger.utils.AccountUtils; import rocks.xmpp.addr.Jid; public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.OnActionExpandListener, TextView.OnEditorActionListener, ChannelDiscoveryService.OnChannelSearchResultsFound, ChannelSearchResultAdapter.OnChannelSearchResultSelected { + private static final String CHANNEL_DISCOVERY_OPT_IN = "channel_discovery_opt_in"; + private final ChannelSearchResultAdapter adapter = new ChannelSearchResultAdapter(); private final PendingItem<String> mInitialSearchValue = new PendingItem<>(); private ActivityChannelDiscoveryBinding binding; private MenuItem mMenuSearchView; private EditText mSearchEditText; + + private ChannelDiscoveryService.Method method = ChannelDiscoveryService.Method.LOCAL_SERVER; + private boolean optedIn = false; @Override @@ -52,14 +59,15 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O @Override void onBackendConnected() { - if (optedIn) { - String query; + if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) { + final String query; if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) { query = mSearchEditText.getText().toString(); } else { query = mInitialSearchValue.peek(); } - xmppConnectionService.discoverChannels(query, this); + toggleLoadingScreen(); + xmppConnectionService.discoverChannels(query, this.method, this); } } @@ -71,7 +79,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O configureActionBar(getSupportActionBar(), true); binding.list.setAdapter(this.adapter); this.adapter.setOnChannelSearchResultSelectedListener(this); - optedIn = getPreferences().getBoolean(CHANNEL_DISCOVERY_OPT_IN, false); + this.optedIn = getPreferences().getBoolean(CHANNEL_DISCOVERY_OPT_IN, false); final String search = savedInstanceState == null ? null : savedInstanceState.getString("search"); if (search != null) { @@ -79,20 +87,30 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O } } + private static ChannelDiscoveryService.Method getMethod(final Context c) { + final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(c); + final String m = p.getString("channel_discovery_method", c.getString(R.string.default_channel_discovery)); + try { + return ChannelDiscoveryService.Method.valueOf(m); + } catch (IllegalArgumentException e) { + return ChannelDiscoveryService.Method.JABBER_NETWORK; + } + } + @Override public boolean onCreateOptionsMenu(final Menu menu) { - getMenuInflater().inflate(R.menu.muc_users_activity, menu); + getMenuInflater().inflate(R.menu.channel_discovery_activity, menu); mMenuSearchView = menu.findItem(R.id.action_search); final View mSearchView = mMenuSearchView.getActionView(); mSearchEditText = mSearchView.findViewById(R.id.search_field); mSearchEditText.setHint(R.string.search_channels); - String initialSearchValue = mInitialSearchValue.pop(); + final String initialSearchValue = mInitialSearchValue.pop(); if (initialSearchValue != null) { mMenuSearchView.expandActionView(); mSearchEditText.append(initialSearchValue); mSearchEditText.requestFocus(); - if (optedIn && xmppConnectionService != null) { - xmppConnectionService.discoverChannels(initialSearchValue, this); + if ((optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) && xmppConnectionService != null) { + xmppConnectionService.discoverChannels(initialSearchValue, this.method, this); } } mSearchEditText.setOnEditorActionListener(this); @@ -116,8 +134,8 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); mSearchEditText.setText(""); toggleLoadingScreen(); - if (optedIn) { - xmppConnectionService.discoverChannels(null, this); + if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) { + xmppConnectionService.discoverChannels(null, this.method, this); } return true; } @@ -125,12 +143,14 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O private void toggleLoadingScreen() { adapter.submitList(Collections.emptyList()); binding.progressBar.setVisibility(View.VISIBLE); + binding.list.setBackgroundColor(StyledAttributes.getColor(this, R.attr.color_background_primary)); } @Override public void onStart() { super.onStart(); - if (!optedIn) { + this.method = getMethod(this); + if (!optedIn && method == ChannelDiscoveryService.Method.JABBER_NETWORK) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.channel_discovery_opt_in_title); builder.setMessage(Html.fromHtml(getString(R.string.channel_discover_opt_in_message))); @@ -155,21 +175,21 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O SharedPreferences preferences = getPreferences(); preferences.edit().putBoolean(CHANNEL_DISCOVERY_OPT_IN, true).apply(); optedIn = true; - xmppConnectionService.discoverChannels(null, this); + xmppConnectionService.discoverChannels(null, this.method, this); } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (optedIn) { + if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) { toggleLoadingScreen(); SoftKeyboardUtils.hideSoftKeyboard(this); - xmppConnectionService.discoverChannels(v.getText().toString(), this); + xmppConnectionService.discoverChannels(v.getText().toString(), this.method, this); } return true; } @Override - public void onChannelSearchResultsFound(final List<MuclumbusService.Room> results) { + public void onChannelSearchResultsFound(final List<Room> results) { runOnUiThread(() -> { adapter.submitList(results); if (results.size() > 0) { @@ -185,7 +205,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O } @Override - public void onChannelSearchResult(final MuclumbusService.Room result) { + public void onChannelSearchResult(final Room result) { List<String> accounts = AccountUtils.getEnabledAccounts(xmppConnectionService); if (accounts.size() == 1) { joinChannelSearchResult(accounts.get(0), result); @@ -202,7 +222,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O @Override public boolean onContextItemSelected(MenuItem item) { - final MuclumbusService.Room room = adapter.getCurrent(); + final Room room = adapter.getCurrent(); if (room != null) { switch (item.getItemId()) { case R.id.share_with: @@ -220,7 +240,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O return false; } - public void joinChannelSearchResult(String selectedAccount, MuclumbusService.Room result) { + public void joinChannelSearchResult(String selectedAccount, Room result) { final Jid jid = Config.DOMAIN_LOCK == null ? Jid.of(selectedAccount) : Jid.of(selectedAccount, Config.DOMAIN_LOCK, null); final boolean syncAutoJoin = getBooleanPreference("autojoin", R.bool.autojoin); final Account account = xmppConnectionService.findAccountByJid(jid); diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java index f3488a3fc..801dc3767 100644 --- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java @@ -7,7 +7,6 @@ import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.IntentSender; import android.content.SharedPreferences; -import androidx.databinding.DataBindingUtil; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; @@ -16,12 +15,6 @@ import android.preference.PreferenceManager; import android.provider.Settings; import android.security.KeyChain; import android.security.KeyChainAliasCallback; -import androidx.annotation.NonNull; -import com.google.android.material.textfield.TextInputLayout; -import androidx.core.content.ContextCompat; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -36,6 +29,15 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.databinding.DataBindingUtil; + +import com.google.android.material.textfield.TextInputLayout; + import org.openintents.openpgp.util.OpenPgpUtils; import java.net.URL; diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ChannelSearchResultAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ChannelSearchResultAdapter.java index 7e5fd4b3e..4094a0168 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ChannelSearchResultAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ChannelSearchResultAdapter.java @@ -17,29 +17,27 @@ import java.util.Locale; import de.pixart.messenger.R; import de.pixart.messenger.databinding.SearchResultItemBinding; -import de.pixart.messenger.http.services.MuclumbusService; +import de.pixart.messenger.entities.Room; import de.pixart.messenger.ui.XmppActivity; import de.pixart.messenger.ui.util.AvatarWorkerTask; import rocks.xmpp.addr.Jid; -public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Room, ChannelSearchResultAdapter.ViewHolder> implements View.OnCreateContextMenuListener { +public class ChannelSearchResultAdapter extends ListAdapter<Room, ChannelSearchResultAdapter.ViewHolder> implements View.OnCreateContextMenuListener { - - private static final DiffUtil.ItemCallback<MuclumbusService.Room> DIFF = new DiffUtil.ItemCallback<MuclumbusService.Room>() { + private static final DiffUtil.ItemCallback<Room> DIFF = new DiffUtil.ItemCallback<Room>() { @Override - public boolean areItemsTheSame(@NonNull MuclumbusService.Room a, @NonNull MuclumbusService.Room b) { + public boolean areItemsTheSame(@NonNull Room a, @NonNull Room b) { return a.address != null && a.address.equals(b.address); } @Override - public boolean areContentsTheSame(@NonNull MuclumbusService.Room a, @NonNull MuclumbusService.Room b) { + public boolean areContentsTheSame(@NonNull Room a, @NonNull Room b) { return a.equals(b); } }; - private OnChannelSearchResultSelected listener; - private MuclumbusService.Room current; + private Room current; public ChannelSearchResultAdapter() { super(DIFF); @@ -53,7 +51,7 @@ public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Roo @Override public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) { - final MuclumbusService.Room searchResult = getItem(position); + final Room searchResult = getItem(position); viewHolder.binding.name.setText(searchResult.getName()); final String description = searchResult.getDescription(); final String language = searchResult.getLanguage(); @@ -66,7 +64,7 @@ public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Roo if (language == null || language.length() != 2) { viewHolder.binding.language.setVisibility(View.GONE); } else { - viewHolder.binding.language.setText("(" + language.toUpperCase(Locale.ENGLISH) + ")"); + viewHolder.binding.language.setText(language.toUpperCase(Locale.ENGLISH)); viewHolder.binding.language.setVisibility(View.VISIBLE); } final Jid room = searchResult.getRoom(); @@ -82,7 +80,7 @@ public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Roo this.listener = listener; } - public MuclumbusService.Room getCurrent() { + public Room getCurrent() { return this.current; } @@ -90,12 +88,16 @@ public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Roo public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { final Activity activity = XmppActivity.find(v); final Object tag = v.getTag(); - if (activity != null && tag instanceof MuclumbusService.Room) { + if (activity != null && tag instanceof Room) { activity.getMenuInflater().inflate(R.menu.channel_item_context, menu); - this.current = (MuclumbusService.Room) tag; + this.current = (Room) tag; } } + public interface OnChannelSearchResultSelected { + void onChannelSearchResult(Room result); + } + public static class ViewHolder extends RecyclerView.ViewHolder { private final SearchResultItemBinding binding; @@ -105,8 +107,4 @@ public class ChannelSearchResultAdapter extends ListAdapter<MuclumbusService.Roo this.binding = binding; } } - - public interface OnChannelSearchResultSelected { - void onChannelSearchResult(MuclumbusService.Room result); - } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/AccountUtils.java b/src/main/java/de/pixart/messenger/utils/AccountUtils.java index 593d3a089..e6e06709e 100644 --- a/src/main/java/de/pixart/messenger/utils/AccountUtils.java +++ b/src/main/java/de/pixart/messenger/utils/AccountUtils.java @@ -2,6 +2,8 @@ package de.pixart.messenger.utils; import android.app.Activity; import android.content.Intent; +import android.view.Menu; +import android.view.MenuItem; import android.widget.Toast; import java.util.ArrayList; diff --git a/src/main/java/de/pixart/messenger/utils/Namespace.java b/src/main/java/de/pixart/messenger/utils/Namespace.java index f8635f21d..a6624309a 100644 --- a/src/main/java/de/pixart/messenger/utils/Namespace.java +++ b/src/main/java/de/pixart/messenger/utils/Namespace.java @@ -35,4 +35,6 @@ public final class Namespace { public static final String MUC_USER = "http://jabber.org/protocol/muc#user"; public static final String BOOKMARKS2 = "urn:xmpp:bookmarks:0"; public static final String BOOKMARKS2_COMPAT = BOOKMARKS2 + "#compat"; + public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items"; + public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info"; } diff --git a/src/main/res/menu/channel_discovery_activity.xml b/src/main/res/menu/channel_discovery_activity.xml index 93e044f27..924953727 100644 --- a/src/main/res/menu/channel_discovery_activity.xml +++ b/src/main/res/menu/channel_discovery_activity.xml @@ -8,4 +8,9 @@ android:title="@string/search" app:actionLayout="@layout/actionview_search" app:showAsAction="collapseActionView|always" /> + <item + android:id="@+id/action_settings" + android:orderInCategory="100" + android:title="@string/action_settings" + app:showAsAction="never" /> </menu>
\ No newline at end of file diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml index fecbff8a4..b0ba7423f 100644 --- a/src/main/res/values/arrays.xml +++ b/src/main/res/values/arrays.xml @@ -125,4 +125,13 @@ <item>@string/medium</item> <item>@string/large</item> </string-array> + + <string-array name="channel_discovery_entries"> + <item>@string/jabber_network</item> + <item>@string/local_server</item> + </string-array> + <string-array name="channel_discover_values"> + <item>JABBER_NETWORK</item> + <item>LOCAL_SERVER</item> + </string-array> </resources> diff --git a/src/main/res/values/defaults.xml b/src/main/res/values/defaults.xml index 7a32d23a3..e1e39fbe5 100644 --- a/src/main/res/values/defaults.xml +++ b/src/main/res/values/defaults.xml @@ -68,6 +68,7 @@ <string name="omemo_setting_default">default_off</string> <string name="theme">auto</string> <string name="default_font_size">small</string> + <string name="default_channel_discovery">JABBER_NETWORK</string> <string-array name="domains"> <item>pix-art.de</item> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 0716acf07..175f1c58b 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -977,4 +977,9 @@ <string name="no_results">No results</string> <string name="file_transmission_cancelled">file transmission cancelled</string> <string name="sharing_application_not_grant_permission">The sharing application did not grant permission to access this file.</string> + <string name="group_chats_and_channels"><![CDATA[Group Chats & Channels]]></string> + <string name="jabber_network">jabber.network</string> + <string name="local_server">Local server</string> + <string name="pref_channel_discovery_summary">Most users should choose ‘jabber.network’ for better suggestions from the entirety of the public XMPP ecosystem.</string> + <string name="pref_channel_discovery">Channel discovery method</string> </resources> diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 5d9e6d6cc..e7149e60c 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -346,6 +346,13 @@ android:key="use_invidious" android:summary="@string/pref_use_invidious_summary" android:title="@string/pref_use_invidious" /> + <ListPreference + android:defaultValue="@string/default_channel_discovery" + android:entries="@array/channel_discovery_entries" + android:entryValues="@array/channel_discover_values" + android:key="channel_discovery_method" + android:summary="@string/pref_channel_discovery_summary" + android:title="@string/pref_channel_discovery" /> <PreferenceCategory android:title="@string/pref_create_backup"> <Preference android:key="create_backup" |