Merge branch 'dev' into strings-squashed

This commit is contained in:
alansley 2024-08-22 13:43:00 +10:00
commit c3c35de408
8 changed files with 75 additions and 65 deletions

View File

@ -10,21 +10,19 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import org.session.libsignal.utilities.SettableFuture
import org.thoughtcrime.securesms.search.SearchRepository
import org.thoughtcrime.securesms.search.model.SearchResult
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@OptIn(ExperimentalCoroutinesApi::class)
@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
// coroutine will be cancelled and the expensive query will not be run.
delay(300)
val settableFuture = SettableFuture<SearchResult>()
searchRepository.query(query.toString(), settableFuture::set)
try {
// search repository doesn't play nicely with suspend functions (yet)
settableFuture.get(10_000, TimeUnit.MILLISECONDS).toGlobalSearchResult()
searchRepository.suspendQuery(query.toString()).toGlobalSearchResult()
} catch (e: Exception) {
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.
* */

View File

@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
@ -29,7 +28,6 @@ import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -40,7 +38,6 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.unit.dp
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.AlertDialog
import org.thoughtcrime.securesms.ui.DialogButtonModel
@ -294,4 +291,3 @@ private val MediaOverviewTab.titleResId: Int
MediaOverviewTab.Media -> R.string.media
MediaOverviewTab.Documents -> R.string.document
}

View File

@ -152,12 +152,30 @@ private fun ThumbnailRow(
) {
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(
painter = painterResource(item.placeholder(LocalContext.current)),
painter = painterResource(placeholder),
contentDescription = null,
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.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.session.libsignal.utilities.Log
import com.opencsv.CSVReader
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ThreadUtils
import java.io.File
import java.io.FileOutputStream
import java.io.FileReader
import java.util.SortedMap
import java.util.TreeMap
class IP2Country private constructor(private val context: Context) {
private val pathsBuiltEventReceiver: BroadcastReceiver
val countryNamesCache = mutableMapOf<String, String>()
private fun Ipv4Int(ip:String) = ip.takeWhile { it != '/' }.split('.').foldIndexed(0L) { i, acc, s ->
val asInt = s.toLong()
acc + (asInt shl (8 * (3-i)))
private fun Ipv4Int(ip: String): Int {
var result = 0L
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 csv = CSVReader(FileReader(file.absoluteFile)).apply {
skip(1)
}
CSVReader(FileReader(file.absoluteFile)).use { csv ->
csv.skip(1)
csv.readAll()
.associate { cols ->
Ipv4Int(cols[0]) to cols[1].toIntOrNull()
csv.asSequence().associateTo(TreeMap()) { cols ->
Ipv4Int(cols[0]).toInt() 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 csv = CSVReader(FileReader(file.absoluteFile)).apply {
skip(1)
}
csv.readAll()
CSVReader(FileReader(file.absoluteFile)).use { csv ->
csv.skip(1)
csv.asSequence()
.filter { cols -> !cols[0].isNullOrEmpty() && !cols[1].isNullOrEmpty() }
.associate { cols ->
cols[0].toInt() to cols[5]
}
}
}
// region Initialization
companion object {
@ -95,9 +114,8 @@ class IP2Country private constructor(private val context: Context) {
// return early if cached
countryNamesCache[ip]?.let { return it }
val comps = ipv4ToCountry.asSequence()
val bestMatchCountry = comps.lastOrNull { it.key <= Ipv4Int(ip) }?.let { (_, code) ->
val ipInt = Ipv4Int(ip)
val bestMatchCountry = ipv4ToCountry.floorEntry(ipInt)?.let { (_, code) ->
if (code != null) {
countryToNames[code]
} else {
@ -127,3 +145,4 @@ class IP2Country private constructor(private val context: Context) {
}
// endregion
}

View File

@ -2,10 +2,7 @@ package org.thoughtcrime.securesms.util;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public final class ListUtil {
@ -21,16 +18,4 @@ public final class ListUtil {
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_marginStart="@dimen/medium_spacing"
android:padding="10dp"
android:src="@drawable/ic_outline_message_requests_24"
android:src="@drawable/ic_message_requests"
android:tint="?unreadIndicatorTextColor"
app:circleColor="?unreadIndicatorBackgroundColor"
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_large">@drawable/ic_document_large_dark</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>
<!-- 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_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_divider_color">@color/core_grey_75</item>
<item name="sticker_management_empty_background_color">@color/core_grey_85</item>