Merge branch 'dev' into release/1.19.2

This commit is contained in:
ThomasSession 2024-08-21 16:33:19 +10:00
commit 20e1fbceb5
7 changed files with 74 additions and 60 deletions

View File

@ -10,21 +10,19 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.session.libsignal.utilities.SettableFuture
import org.thoughtcrime.securesms.search.SearchRepository import org.thoughtcrime.securesms.search.SearchRepository
import org.thoughtcrime.securesms.search.model.SearchResult import org.thoughtcrime.securesms.search.model.SearchResult
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel @HiltViewModel
@ -47,11 +45,8 @@ class GlobalSearchViewModel @Inject constructor(
// User input delay in case we get a new query within a few hundred ms this // User input delay in case we get a new query within a few hundred ms this
// coroutine will be cancelled and the expensive query will not be run. // coroutine will be cancelled and the expensive query will not be run.
delay(300) delay(300)
val settableFuture = SettableFuture<SearchResult>()
searchRepository.query(query.toString(), settableFuture::set)
try { try {
// search repository doesn't play nicely with suspend functions (yet) searchRepository.suspendQuery(query.toString()).toGlobalSearchResult()
settableFuture.get(10_000, TimeUnit.MILLISECONDS).toGlobalSearchResult()
} catch (e: Exception) { } catch (e: Exception) {
GlobalSearchResult(query.toString()) GlobalSearchResult(query.toString())
} }
@ -69,6 +64,12 @@ class GlobalSearchViewModel @Inject constructor(
} }
} }
private suspend fun SearchRepository.suspendQuery(query: String): SearchResult {
return suspendCoroutine { cont ->
query(query, cont::resume)
}
}
/** /**
* Re-emit whenever refreshes emits. * Re-emit whenever refreshes emits.
* */ * */

View File

@ -152,12 +152,30 @@ private fun ThumbnailRow(
) { ) {
it.diskCacheStrategy(DiskCacheStrategy.NONE) it.diskCacheStrategy(DiskCacheStrategy.NONE)
} }
} else if (item.hasPlaceholder) { } else {
// The resource given by the placeholder needs tinting according to our theme.
// But the missing thumbnail picture does not.
var (placeholder, shouldTint) = if (item.hasPlaceholder) {
item.placeholder(LocalContext.current) to true
} else {
R.drawable.ic_missing_thumbnail_picture to false
}
if (placeholder == 0) {
placeholder = R.drawable.ic_missing_thumbnail_picture
shouldTint = false
}
Image( Image(
painter = painterResource(item.placeholder(LocalContext.current)), painter = painterResource(placeholder),
contentDescription = null, contentDescription = null,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Inside contentScale = ContentScale.Inside,
colorFilter = if (shouldTint) {
ColorFilter.tint(LocalColors.current.textSecondary)
} else {
null
}
) )
} }

View File

@ -5,46 +5,65 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.session.libsignal.utilities.Log
import com.opencsv.CSVReader import com.opencsv.CSVReader
import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.FileReader import java.io.FileReader
import java.util.SortedMap
import java.util.TreeMap
class IP2Country private constructor(private val context: Context) { class IP2Country private constructor(private val context: Context) {
private val pathsBuiltEventReceiver: BroadcastReceiver private val pathsBuiltEventReceiver: BroadcastReceiver
val countryNamesCache = mutableMapOf<String, String>() val countryNamesCache = mutableMapOf<String, String>()
private fun Ipv4Int(ip:String) = ip.takeWhile { it != '/' }.split('.').foldIndexed(0L) { i, acc, s -> private fun Ipv4Int(ip: String): Int {
val asInt = s.toLong() var result = 0L
acc + (asInt shl (8 * (3-i))) var currentValue = 0L
var octetIndex = 0
for (char in ip) {
if (char == '.' || char == '/') {
result = result or (currentValue shl (8 * (3 - octetIndex)))
currentValue = 0
octetIndex++
if (char == '/') break
} else {
currentValue = currentValue * 10 + (char - '0')
}
} }
private val ipv4ToCountry by lazy { // Handle the last octet
result = result or (currentValue shl (8 * (3 - octetIndex)))
return result.toInt()
}
private val ipv4ToCountry: TreeMap<Int, Int?> by lazy {
val file = loadFile("geolite2_country_blocks_ipv4.csv") val file = loadFile("geolite2_country_blocks_ipv4.csv")
val csv = CSVReader(FileReader(file.absoluteFile)).apply { CSVReader(FileReader(file.absoluteFile)).use { csv ->
skip(1) csv.skip(1)
}
csv.readAll() csv.asSequence().associateTo(TreeMap()) { cols ->
.associate { cols -> Ipv4Int(cols[0]).toInt() to cols[1].toIntOrNull()
Ipv4Int(cols[0]) to cols[1].toIntOrNull() }
} }
} }
private val countryToNames by lazy { private val countryToNames: Map<Int, String> by lazy {
val file = loadFile("geolite2_country_locations_english.csv") val file = loadFile("geolite2_country_locations_english.csv")
val csv = CSVReader(FileReader(file.absoluteFile)).apply { CSVReader(FileReader(file.absoluteFile)).use { csv ->
skip(1) csv.skip(1)
}
csv.readAll() csv.asSequence()
.filter { cols -> !cols[0].isNullOrEmpty() && !cols[1].isNullOrEmpty() } .filter { cols -> !cols[0].isNullOrEmpty() && !cols[1].isNullOrEmpty() }
.associate { cols -> .associate { cols ->
cols[0].toInt() to cols[5] cols[0].toInt() to cols[5]
} }
} }
}
// region Initialization // region Initialization
companion object { companion object {
@ -95,9 +114,8 @@ class IP2Country private constructor(private val context: Context) {
// return early if cached // return early if cached
countryNamesCache[ip]?.let { return it } countryNamesCache[ip]?.let { return it }
val comps = ipv4ToCountry.asSequence() val ipInt = Ipv4Int(ip)
val bestMatchCountry = ipv4ToCountry.floorEntry(ipInt)?.let { (_, code) ->
val bestMatchCountry = comps.lastOrNull { it.key <= Ipv4Int(ip) }?.let { (_, code) ->
if (code != null) { if (code != null) {
countryToNames[code] countryToNames[code]
} else { } else {
@ -127,3 +145,4 @@ class IP2Country private constructor(private val context: Context) {
} }
// endregion // endregion
} }

View File

@ -2,10 +2,7 @@ package org.thoughtcrime.securesms.util;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
public final class ListUtil { public final class ListUtil {
@ -21,16 +18,4 @@ public final class ListUtil {
return chunks; return chunks;
} }
@SafeVarargs
public static <T> List<T> concat(Collection<T>... items) {
//noinspection Convert2MethodRef
final List<T> concat = new ArrayList<>(Stream.of(items).map(Collection::size).reduce(0, (lhs, rhs) -> lhs+rhs));
for (Collection<T> list : items) {
concat.addAll(list);
}
return concat;
}
} }

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,2H4C2.9,2 2,2.9 2,4V22L6,18H20C21.1,18 22,17.1 22,16V4C22,2.9 21.1,2 20,2M20,16H5.2L4,17.2V4H20V16M12.2,5.5C11.3,5.5 10.6,5.7 10.1,6C9.5,6.4 9.2,7 9.3,7.7H11.3C11.3,7.4 11.4,7.2 11.6,7.1C11.8,7 12,6.9 12.3,6.9C12.6,6.9 12.9,7 13.1,7.2C13.3,7.4 13.4,7.6 13.4,7.9C13.4,8.2 13.3,8.4 13.2,8.6C13,8.8 12.8,9 12.6,9.1C12.1,9.4 11.7,9.7 11.5,9.9C11.1,10.2 11,10.5 11,11H13C13,10.7 13.1,10.5 13.1,10.3C13.2,10.1 13.4,10 13.6,9.8C14.1,9.6 14.4,9.3 14.7,8.9C15,8.5 15.1,8.1 15.1,7.7C15.1,7 14.8,6.4 14.3,6C13.9,5.7 13.1,5.5 12.2,5.5M11,12V14H13V12H11Z" />
</vector>

View File

@ -18,7 +18,7 @@
android:layout_marginVertical="@dimen/medium_spacing" android:layout_marginVertical="@dimen/medium_spacing"
android:layout_marginStart="@dimen/medium_spacing" android:layout_marginStart="@dimen/medium_spacing"
android:padding="10dp" android:padding="10dp"
android:src="@drawable/ic_outline_message_requests_24" android:src="@drawable/ic_message_requests"
android:tint="?unreadIndicatorTextColor" android:tint="?unreadIndicatorTextColor"
app:circleColor="?unreadIndicatorBackgroundColor" app:circleColor="?unreadIndicatorBackgroundColor"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@ -58,6 +58,9 @@
<item name="attachment_document_icon_small">@drawable/ic_document_small_dark</item> <item name="attachment_document_icon_small">@drawable/ic_document_small_dark</item>
<item name="attachment_document_icon_large">@drawable/ic_document_large_dark</item> <item name="attachment_document_icon_large">@drawable/ic_document_large_dark</item>
<item name="colorError">?danger</item> <item name="colorError">?danger</item>
<item name="conversation_icon_attach_audio">@drawable/ic_audio_dark</item>
<item name="conversation_icon_attach_video">@drawable/ic_video_dark</item>
</style> </style>
<!-- This should be the default theme for the application. --> <!-- This should be the default theme for the application. -->
@ -206,9 +209,6 @@
<item name="linkpreview_secondary_text_color">?android:textColorPrimary</item> <item name="linkpreview_secondary_text_color">?android:textColorPrimary</item>
<item name="linkpreview_divider_color">@color/transparent</item> <item name="linkpreview_divider_color">@color/transparent</item>
<item name="conversation_icon_attach_audio">@drawable/ic_audio_dark</item>
<item name="conversation_icon_attach_video">@drawable/ic_video_dark</item>
<item name="sticker_management_icon">@drawable/sticker_button_dark</item> <item name="sticker_management_icon">@drawable/sticker_button_dark</item>
<item name="sticker_management_divider_color">@color/core_grey_75</item> <item name="sticker_management_divider_color">@color/core_grey_75</item>
<item name="sticker_management_empty_background_color">@color/core_grey_85</item> <item name="sticker_management_empty_background_color">@color/core_grey_85</item>