mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-28 20:45:17 +00:00
Merge branch 'dev' into release/1.19.2
This commit is contained in:
commit
20e1fbceb5
@ -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.
|
||||||
* */
|
* */
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,45 +5,64 @@ 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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the last octet
|
||||||
|
result = result or (currentValue shl (8 * (3 - octetIndex)))
|
||||||
|
|
||||||
|
return result.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ipv4ToCountry by lazy {
|
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
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
|
@ -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"
|
||||||
|
@ -58,7 +58,10 @@
|
|||||||
<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>
|
||||||
</style>
|
|
||||||
|
<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. -->
|
<!-- This should be the default theme for the application. -->
|
||||||
<style name="Theme.Session.DayNight" parent="Base.Theme.Session">
|
<style name="Theme.Session.DayNight" parent="Base.Theme.Session">
|
||||||
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user