diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt index f309458b0d6dbab0180a6cc12f153f3b66c3eba7..89e74d0d4b4b1adc4b939cac12db79444dcc84f8 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt @@ -26,9 +26,7 @@ import de.kuschku.quasseldroid.util.helper.editApply import de.kuschku.quasseldroid.util.helper.editCommit import de.kuschku.quasseldroid.util.helper.sharedPreferences import de.kuschku.quasseldroid.util.helper.toLiveData -import io.reactivex.Observable -import io.reactivex.functions.BiFunction -import io.reactivex.subjects.BehaviorSubject +import io.reactivex.subjects.PublishSubject import org.threeten.bp.Instant import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit @@ -220,7 +218,7 @@ class QuasselService : DaggerLifecycleService(), } } } - private val connectivity = BehaviorSubject.createDefault(Unit) + private val connectivity = PublishSubject.create<Unit>() private fun disconnectFromCore() { getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE).editCommit { @@ -264,18 +262,27 @@ class QuasselService : DaggerLifecycleService(), } }) - Observable.combineLatest( - sessionManager.state.filter { it == ConnectionState.DISCONNECTED || it == ConnectionState.CLOSED }, - connectivity, - BiFunction { a: ConnectionState, _: Unit -> a }) + var wasEverConnected = false + connectivity + .delay(200, TimeUnit.MILLISECONDS) + .throttleFirst(1, TimeUnit.SECONDS) + .toLiveData() + .observe(this, Observer { + if (wasEverConnected) sessionManager.reconnect(true) + }) + + sessionManager.state .distinctUntilChanged() .delay(200, TimeUnit.MILLISECONDS) .throttleFirst(1, TimeUnit.SECONDS) .toLiveData() .observe( this, Observer { - if (it == ConnectionState.DISCONNECTED || it == ConnectionState.CLOSED) - sessionManager.reconnect(true) + if (it == ConnectionState.DISCONNECTED || it == ConnectionState.CLOSED) { + if (wasEverConnected) sessionManager.reconnect() + } else { + wasEverConnected = true + } }) sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt index d6ca32c1cc26db02341c1fb691a3f25a0df4dfc7..5cdbe638ad0ca5f04186b7c9f6d29bd2ac93661c 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt @@ -388,6 +388,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc val selected = dialog.selectedIndices ?: emptyArray() runInBackground { val newlyFiltered = selected + .asSequence() .map { flags[it] } .fold(Message_Type.of()) { acc, i -> acc or i } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt index f0dcff4b0c376b0a176e39dc6af3ebd8312a8adb..47733365546b7ab01443a624d67b58c96505519f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt @@ -210,41 +210,44 @@ class BufferViewConfigFragment : ServiceBoundFragment() { val (config, list) = info ?: Pair(null, emptyList()) val minimumActivity = config?.minimumActivity() ?: Buffer_Activity.NONE val activities = activityList.associate { it.bufferId to it.filtered } - listAdapter.submitList(list.sortedBy { props -> - !props.info.type.hasFlag(Buffer_Type.StatusBuffer) - }.sortedBy { props -> - props.network.networkName - }.map { props -> - BufferListItem( - props, - BufferState( - networkExpanded = !collapsedNetworks.contains(props.network.networkId), - selected = selected.info?.bufferId == props.info.bufferId - ) - ) - }.filter { (props, state) -> - props.info.type.hasFlag(BufferInfo.Type.StatusBuffer) || state.networkExpanded - }.map { - val activity = it.props.activity - (activities[it.props.info.bufferId] ?: 0) - it.copy( - props = it.props.copy( - activity = activity, - bufferActivity = Buffer_Activity.of( - when { - it.props.highlights > 0 -> Buffer_Activity.Highlight - activity.hasFlag(Message_Type.Plain) || - activity.hasFlag(Message_Type.Notice) || - activity.hasFlag(Message_Type.Action) -> Buffer_Activity.NewMessage - activity.isNotEmpty() -> Buffer_Activity.OtherActivity - else -> Buffer_Activity.NoActivity - } + activity?.runOnUiThread { + listAdapter.submitList(list.asSequence().sortedBy { props -> + !props.info.type.hasFlag(Buffer_Type.StatusBuffer) + }.sortedBy { props -> + props.network.networkName + }.map { props -> + val activity = props.activity - (activities[props.info.bufferId] ?: 0) + BufferListItem( + props.copy( + activity = activity, + description = ircFormatDeserializer.formatString( + requireContext(), + props.description.toString(), + colorize = messageSettings.colorizeMirc + ), + bufferActivity = Buffer_Activity.of( + when { + props.highlights > 0 -> Buffer_Activity.Highlight + activity.hasFlag(Message_Type.Plain) || + activity.hasFlag(Message_Type.Notice) || + activity.hasFlag(Message_Type.Action) -> Buffer_Activity.NewMessage + activity.isNotEmpty() -> Buffer_Activity.OtherActivity + else -> Buffer_Activity.NoActivity + } + ) + ), + BufferState( + networkExpanded = !collapsedNetworks.contains(props.network.networkId), + selected = selected.info?.bufferId == props.info.bufferId ) ) - ) - }.filter { - minimumActivity.toInt() <= it.props.bufferActivity.toInt() || - it.props.info.type.hasFlag(Buffer_Type.StatusBuffer) - }) + }.filter { (props, state) -> + props.info.type.hasFlag(BufferInfo.Type.StatusBuffer) || state.networkExpanded + }.filter { + minimumActivity.toInt() <= it.props.bufferActivity.toInt() || + it.props.info.type.hasFlag(Buffer_Type.StatusBuffer) + }.toList()) + } } } }) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt index be99fcca202bfe5301e7f71762c01fa2ddd3bc78..bafac822081ca1fe1fc10d06eeb5b1141c72b3ef 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt @@ -120,6 +120,7 @@ class AutoCompleteHelper( )?.let { ircChannel -> val users = ircChannel.ircUsers() val buffers = infos + .asSequence() .filter { it.type.toInt() == Buffer_Type.ChannelBuffer.toInt() }.mapNotNull { info -> @@ -136,7 +137,7 @@ class AutoCompleteHelper( description = channel.topic() ) } - val nicks = users.map { user -> + val nicks = users.asSequence().map { user -> val userModes = ircChannel.userModes(user) val prefixModes = network.prefixModes() @@ -160,7 +161,7 @@ class AutoCompleteHelper( lastWord.first.trimStart(*IGNORED_CHARS), ignoreCase = true ) - }.sorted() + }.sorted().toList() } } else null } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt index 6a6be7434374a8fd244d1bb9d9e0e50d2504f1ad..dc30cff52db1650f76b1363ceed72df3ad7e1108 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt @@ -85,7 +85,7 @@ class MessageListFragment : ServiceBoundFragment() { override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = when (item?.itemId) { R.id.action_copy -> { val builder = SpannableStringBuilder() - viewModel.selectedMessages.value.values.sortedBy { + viewModel.selectedMessages.value.values.asSequence().sortedBy { it.id }.map { if (it.name != null && it.content != null) { @@ -114,7 +114,7 @@ class MessageListFragment : ServiceBoundFragment() { } R.id.action_share -> { val builder = SpannableStringBuilder() - viewModel.selectedMessages.value.values.sortedBy { + viewModel.selectedMessages.value.values.asSequence().sortedBy { it.id }.map { if (it.name != null && it.content != null) { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt index 68408c557cb4cdb1d45c7e821b9c9aff89492a3d..40b5c4861abda3c03cec2536d677e95d53604448 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt @@ -77,7 +77,7 @@ class NickListFragment : ServiceBoundFragment() { val avatarSize = resources.getDimensionPixelSize(R.dimen.avatar_size) viewModel.nickData.toLiveData().observe(this, Observer { runInBackground { - it?.map { + it?.asSequence()?.map { val nickName = it.nick val senderColorIndex = IrcUserUtils.senderColor(nickName) val rawInitial = nickName.trimStart('-', @@ -129,7 +129,11 @@ class NickListFragment : ServiceBoundFragment() { .trimStart(*IGNORED_CHARS) }?.sortedBy { it.lowestMode - }?.let(nickListAdapter::submitList) + }?.toList()?.let { + activity?.runOnUiThread { + nickListAdapter.submitList(it) + } + } } }) savedInstanceState?.run { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragment.kt index 1a0bb25ffb276d1e488d2fe57de8f07cf7a0d7c6..e734b759a85b82b3de528024650823c4a2864528 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragment.kt @@ -46,7 +46,7 @@ class AppSettingsFragment : DaggerPreferenceFragmentCompat(), fun initSummary(preference: Preference) { if (preference is PreferenceGroup) { - (0 until preference.preferenceCount).map(preference::getPreference).forEach(::initSummary) + (0 until preference.preferenceCount).asSequence().map(preference::getPreference).forEach(::initSummary) } else { updateSummary(preference) } diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt index afccc5d1627be0735750ac987344cb0eced0fe92..4c79361d6ff68004d63d408f25e639c72b7b3432 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt @@ -61,7 +61,7 @@ object AvatarHelper { ?: return emptyList() if (size != null) { - return listOf("https://static.irccloud-cdn.com/avatar-redirect/w${truncateSize(size)}/$userId") + return listOf("https://static.irccloud-cdn.com/avatar-redirect/s${truncateSize(size)}/$userId") } return listOf("https://static.irccloud-cdn.com/avatar-redirect/$userId") diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/Patterns.kt b/app/src/main/java/de/kuschku/quasseldroid/util/Patterns.kt index 85dd63f9bf231db06f4c3ae332cfa00c10b929cb..22204ebd612f8c00438d9d893740baf1359f4c79 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/Patterns.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/Patterns.kt @@ -48,9 +48,7 @@ object Patterns { @Language("RegExp") const val LOCAL_HOST_NAME = """(?:$IRI_LABEL\.)*$IRI_LABEL""" - @Language("RegExp") - const val DOMAIN_NAME_STR = """(?:$LOCAL_HOST_NAME|$HOST_NAME|$IP_ADDRESS_STRING)""" - val DOMAIN_NAME = Regex(DOMAIN_NAME_STR) + val DOMAIN_NAME = Regex("""(?:$LOCAL_HOST_NAME|$HOST_NAME|$IP_ADDRESS_STRING)""") /** * Regular expression for valid email characters. Does not include some of the valid characters * defined in RFC5321: #&~!^`{}/=$*?| @@ -72,12 +70,12 @@ object Patterns { @Language("RegExp") const val EMAIL_ADDRESS_DOMAIN = """(?=.{1,255}(?:\s|$|^))$HOST_NAME""" + /** * Regular expression pattern to match email addresses. It excludes double quoted local parts * and the special characters #&~!^`{}/=$*?| that are included in RFC5321. */ - const val AUTOLINK_EMAIL_ADDRESS_STR = """($WORD_BOUNDARY(?:$EMAIL_ADDRESS_LOCAL_PART@$EMAIL_ADDRESS_DOMAIN)$WORD_BOUNDARY)""" - val AUTOLINK_EMAIL_ADDRESS = Regex(AUTOLINK_EMAIL_ADDRESS_STR) + val AUTOLINK_EMAIL_ADDRESS = Regex("""($WORD_BOUNDARY(?:$EMAIL_ADDRESS_LOCAL_PART@$EMAIL_ADDRESS_DOMAIN)$WORD_BOUNDARY)""") /** * Regular expression pattern to match IRCCloud user idents. diff --git a/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt b/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..bde949d544ec40008a5869a24098587f4c549f08 --- /dev/null +++ b/app/src/test/java/de/kuschku/quasseldroid/util/AvatarHelperTest.kt @@ -0,0 +1,121 @@ +package de.kuschku.quasseldroid.util + +import de.kuschku.libquassel.protocol.Message_Flag +import de.kuschku.libquassel.protocol.Message_Type +import de.kuschku.quasseldroid.persistence.QuasselDatabase +import de.kuschku.quasseldroid.settings.MessageSettings +import org.junit.Test +import org.threeten.bp.Instant + +class AvatarHelperTest { + @Test + fun testGravatar() { + val message = QuasselDatabase.DatabaseMessage( + messageId = 1, + time = Instant.now(), + type = Message_Type.of(Message_Type.Plain).toInt(), + flag = Message_Flag.of().toInt(), + bufferId = 0, + sender = "justJanne", + senderPrefixes = "", + realName = "Janne Koschinski <janne@kuschku.de>", + avatarUrl = "", + content = "Lorem Ipsum I Dolor Sit Amet", + ignored = false + ) + + assert( + AvatarHelper.avatar( + MessageSettings( + showGravatarAvatars = true, + showIRCCloudAvatars = true + ), + message + ).contains("https://www.gravatar.com/avatar/81128f11cae692bc486e3f88b854ddf1?d=404") + ) + + assert( + AvatarHelper.avatar( + MessageSettings( + showGravatarAvatars = false, + showIRCCloudAvatars = false + ), + message + ).isEmpty() + ) + } + + @Test + fun testIrcCloud() { + val message = QuasselDatabase.DatabaseMessage( + messageId = 1, + time = Instant.now(), + type = Message_Type.of(Message_Type.Plain).toInt(), + flag = Message_Flag.of().toInt(), + bufferId = 0, + sender = "jwheare!sid2@irccloud.com", + senderPrefixes = "", + realName = "James Wheare", + avatarUrl = "", + content = "Lorem Ipsum I Dolor Sit Amet", + ignored = false + ) + + assert( + AvatarHelper.avatar( + MessageSettings( + showGravatarAvatars = true, + showIRCCloudAvatars = true + ), + message + ).contains("https://static.irccloud-cdn.com/avatar-redirect/2") + ) + + assert( + AvatarHelper.avatar( + MessageSettings( + showGravatarAvatars = false, + showIRCCloudAvatars = false + ), + message + ).isEmpty() + ) + } + + @Test + fun testActualAvatars() { + val message = QuasselDatabase.DatabaseMessage( + messageId = 1, + time = Instant.now(), + type = Message_Type.of(Message_Type.Plain).toInt(), + flag = Message_Flag.of().toInt(), + bufferId = 0, + sender = "jwheare!sid2@irccloud.com", + senderPrefixes = "", + realName = "James Wheare", + avatarUrl = "https://quasseldroid.info/favicon.png", + content = "Lorem Ipsum I Dolor Sit Amet", + ignored = false + ) + + assert( + AvatarHelper.avatar( + MessageSettings( + showGravatarAvatars = true, + showIRCCloudAvatars = true + ), + message + ).contains("https://quasseldroid.info/favicon.png") + ) + + assert( + AvatarHelper.avatar( + MessageSettings( + showGravatarAvatars = false, + showIRCCloudAvatars = false + ), + message + ) == listOf("https://quasseldroid.info/favicon.png") + ) + } +} diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt index c10ac00b82414e48f54e23df9ebe5cb25e80c4bf..5dd5851a24ebad8ed4ee0028dd445aeeff0fa1be 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt @@ -64,10 +64,10 @@ class SessionManager( Invokers } - fun ifDisconnected(closure: () -> Unit) { + fun ifDisconnected(closure: (ISession) -> Unit) { state.or(ConnectionState.DISCONNECTED).let { if (it == ConnectionState.DISCONNECTED || it == ConnectionState.CLOSED) { - closure() + closure(inProgressSession.value) } } } diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt index 8332bff62858ee410049c79efb0a8742e6f938db..d0434bd8f62862a248e4b2a99675160e294a024b 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt @@ -185,7 +185,7 @@ class QuasselViewModel : ViewModel() { val userModes = ircChannel.userModes(user) val prefixModes = network.prefixModes() - val lowestMode = userModes.mapNotNull { + val lowestMode = userModes.asSequence().mapNotNull { prefixModes.indexOf(it) }.min() ?: prefixModes.size @@ -300,7 +300,7 @@ class QuasselViewModel : ViewModel() { ) ).switchMap { (ids, temp, perm) -> fun transformIds(ids: Collection<BufferId>, state: BufferHiddenState) = - ids.mapNotNull { id -> + ids.asSequence().mapNotNull { id -> bufferSyncer.bufferInfo(id) }.filter { currentConfig.networkId() <= 0 || currentConfig.networkId() == it.networkId @@ -403,10 +403,10 @@ class QuasselViewModel : ViewModel() { transformIds(ids.distinct(), BufferHiddenState.VISIBLE) } - combineLatest<BufferProps>(buffers).map { list -> + combineLatest<BufferProps>(buffers.toList()).map { list -> Pair<BufferViewConfig?, List<BufferProps>>( config, - list.filter { + list.asSequence().filter { (!config.hideInactiveBuffers()) || it.bufferStatus != BufferStatus.OFFLINE || it.info.type.hasFlag(Buffer_Type.StatusBuffer) @@ -415,7 +415,7 @@ class QuasselViewModel : ViewModel() { it.sortedBy { IrcCaseMappers.unicode.toLowerCaseNullable(it.info.bufferName) } .sortedByDescending { it.hiddenState == BufferHiddenState.VISIBLE } else it - }.distinctBy { it.info.bufferId } + }.distinctBy { it.info.bufferId }.toList() ) } }