Merge branch 'main' into claude/auto-load-transactions-scroll-KC8of

This commit is contained in:
Vitor Pamplona
2026-03-17 11:48:21 -04:00
committed by GitHub
23 changed files with 307 additions and 38 deletions
Regular → Executable
View File
+1 -1
View File
@@ -16,7 +16,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "./gradlew spotlessApply", "command": "./gradlew spotlessApply 2>/dev/null || spotless-apply",
"timeout": 120 "timeout": 120
} }
] ]
+19 -6
View File
@@ -1,4 +1,4 @@
name: Build Benchmark APK name: Build APK For Claude
on: on:
push: push:
@@ -43,14 +43,27 @@ jobs:
name: Play Benchmark APK name: Play Benchmark APK
path: amethyst/build/outputs/apk/play/benchmark/amethyst-play-universal-benchmark.apk path: amethyst/build/outputs/apk/play/benchmark/amethyst-play-universal-benchmark.apk
- name: Comment on commit with APK link - name: Comment on PR with APK link
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
script: | script: |
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; const artifactId = `${{ steps.upload.outputs.artifact-id }}`;
await github.rest.repos.createCommitComment({ const downloadUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/artifacts/${artifactId}`;
const body = `📦 **Benchmark APK ready!**\n\nDownload: [Play Benchmark APK](${downloadUrl})`;
const branch = context.ref.replace('refs/heads/', '');
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
commit_sha: context.sha, head: `${context.repo.owner}:${branch}`,
body: `📦 **Benchmark APK ready!**\n\nDownload it from the [workflow run artifacts](${runUrl}#artifacts).` state: 'open'
}); });
for (const pr of prs) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body
});
}
@@ -119,7 +119,8 @@ object ShareHelper {
bytesRead >= 12 && matchesMagicNumbers(header, 4, MOV_FTYP) -> detectMp4OrMov(header) bytesRead >= 12 && matchesMagicNumbers(header, 4, MOV_FTYP) -> detectMp4OrMov(header)
// MP4/MOV alternative: moov, mdat, or free at offset 4 // MP4/MOV alternative: moov, mdat, or free at offset 4
bytesRead >= 8 && ( bytesRead >= 8 &&
(
matchesMagicNumbers(header, 4, MOV_MOOV) || matchesMagicNumbers(header, 4, MOV_MOOV) ||
matchesMagicNumbers(header, 4, MOV_MDAT) || matchesMagicNumbers(header, 4, MOV_MDAT) ||
matchesMagicNumbers(header, 4, MOV_FREE) matchesMagicNumbers(header, 4, MOV_FREE)
@@ -403,7 +403,16 @@ class ChatNewMessageViewModel :
accountViewModel.launchSigner { accountViewModel.launchSigner {
if (nip17) { if (nip17) {
ChatFileUploader(account).justUploadNIP17(uploadState, onError, context) { ChatFileUploader(account).justUploadNIP17(
uploadState,
onError,
onEncryptedUploadError = { title, message ->
encryptedUploadErrorTitle = title
encryptedUploadErrorMessage = message
pendingRetryMode = RetryMode.HOLD
},
context,
) {
uploadsWaitingToBeSent += it uploadsWaitingToBeSent += it
draftTag.newVersion() draftTag.newVersion()
onceUploaded() onceUploaded()
@@ -428,7 +437,19 @@ class ChatNewMessageViewModel :
accountViewModel.launchSigner { accountViewModel.launchSigner {
if (nip17) { if (nip17) {
ChatFileUploader(account).justUploadNIP17(uploadState, onError, context) { ChatFileUploader(account).justUploadNIP17(
uploadState,
onError,
onEncryptedUploadError = { title, message ->
encryptedUploadErrorTitle = title
encryptedUploadErrorMessage = message
pendingRetryMode = RetryMode.SEND
pendingRetryOnError = onError
pendingRetryContext = context
pendingRetryOnceUploaded = onceUploaded
},
context,
) {
ChatFileSender(room, account).sendNIP17(it) ChatFileSender(room, account).sendNIP17(it)
draftTag.newVersion() draftTag.newVersion()
onceUploaded() onceUploaded()
@@ -443,6 +464,69 @@ class ChatNewMessageViewModel :
} }
} }
// Encrypted upload error state for retry dialog
var encryptedUploadErrorTitle by mutableStateOf<String?>(null)
var encryptedUploadErrorMessage by mutableStateOf<String?>(null)
var pendingRetryMode by mutableStateOf<RetryMode?>(null)
var pendingRetryOnError by mutableStateOf<((String, String) -> Unit)?>(null)
var pendingRetryContext by mutableStateOf<Context?>(null)
var pendingRetryOnceUploaded by mutableStateOf<(() -> Unit)?>(null)
enum class RetryMode { HOLD, SEND }
fun dismissEncryptedUploadError() {
encryptedUploadErrorTitle = null
encryptedUploadErrorMessage = null
pendingRetryMode = null
pendingRetryOnError = null
pendingRetryContext = null
pendingRetryOnceUploaded = null
}
fun retryWithoutEncryption() {
val mode = pendingRetryMode ?: return
val onError = pendingRetryOnError
val context = pendingRetryContext
val onceUploaded = pendingRetryOnceUploaded
val room = room
val uploadState = uploadState
dismissEncryptedUploadError()
if (uploadState == null || context == null) return
uploadState.encryptFiles = false
accountViewModel.launchSigner {
when (mode) {
RetryMode.HOLD -> {
ChatFileUploader(account).justUploadNIP17Unencrypted(
uploadState,
onError ?: accountViewModel.toastManager::toast,
context,
) {
uploadsWaitingToBeSent += it
draftTag.newVersion()
onceUploaded?.invoke()
}
}
RetryMode.SEND -> {
if (room == null) return@launchSigner
ChatFileUploader(account).justUploadNIP17Unencrypted(
uploadState,
onError ?: accountViewModel.toastManager::toast,
context,
) {
ChatFileSender(room, account).sendNIP17(it)
draftTag.newVersion()
onceUploaded?.invoke()
}
}
}
}
}
private suspend fun innerSendPost(draftTag: String?) { private suspend fun innerSendPost(draftTag: String?) {
val room = room ?: return val room = room ?: return
@@ -563,6 +647,8 @@ class ChatNewMessageViewModel :
uploadsWaitingToBeSent = emptyList() uploadsWaitingToBeSent = emptyList()
uploadState?.reset() uploadState?.reset()
dismissEncryptedUploadError()
iMetaAttachments.reset() iMetaAttachments.reset()
emojiSuggestions?.reset() emojiSuggestions?.reset()
@@ -302,6 +302,15 @@ fun GroupDMScreenContent(
) )
} }
} }
postViewModel.encryptedUploadErrorTitle?.let { title ->
EncryptedUploadErrorDialog(
title = title,
message = postViewModel.encryptedUploadErrorMessage ?: "",
onDismiss = postViewModel::dismissEncryptedUploadError,
onRetryWithoutEncryption = postViewModel::retryWithoutEncryption,
)
}
} }
} }
@@ -27,8 +27,10 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -116,6 +118,15 @@ fun PrivateMessageEditFieldRow(
} }
} }
channelScreenModel.encryptedUploadErrorTitle?.let { title ->
EncryptedUploadErrorDialog(
title = title,
message = channelScreenModel.encryptedUploadErrorMessage ?: "",
onDismiss = channelScreenModel::dismissEncryptedUploadError,
onRetryWithoutEncryption = channelScreenModel::retryWithoutEncryption,
)
}
Column( Column(
modifier = EditFieldModifier, modifier = EditFieldModifier,
) { ) {
@@ -217,3 +228,36 @@ fun KeyboardLeadingIcon(
ToggleNip17Button(channelScreenModel, accountViewModel) ToggleNip17Button(channelScreenModel, accountViewModel)
} }
} }
@Composable
fun EncryptedUploadErrorDialog(
title: String,
message: String,
onDismiss: () -> Unit,
onRetryWithoutEncryption: () -> Unit,
) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(title) },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(message)
Text(
stringRes(R.string.upload_without_encryption_warning),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error,
)
}
},
confirmButton = {
TextButton(onClick = onRetryWithoutEncryption) {
Text(stringRes(R.string.retry_without_encryption))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringRes(R.string.cancel))
}
},
)
}
@@ -24,11 +24,14 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
import com.vitorpamplona.quartz.nip01Core.tags.references.references
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.utils.ciphers.AESGCM import com.vitorpamplona.quartz.utils.ciphers.AESGCM
class ChatFileSender( class ChatFileSender(
@@ -39,6 +42,8 @@ class ChatFileSender(
uploads.forEach { uploads.forEach {
if (it.cipher != null) { if (it.cipher != null) {
sendNIP17(it.result, it.caption, it.contentWarningReason, it.cipher) sendNIP17(it.result, it.caption, it.contentWarningReason, it.cipher)
} else {
sendNIP17AsHiddenLink(it.result, it.caption, it.contentWarningReason)
} }
} }
} }
@@ -70,6 +75,31 @@ class ChatFileSender(
) )
} }
suspend fun sendNIP17AsHiddenLink(
result: UploadOrchestrator.OrchestratorResult.ServerResult,
caption: String?,
contentWarningReason: String?,
) {
val iMetaAttachments = IMetaAttachments()
iMetaAttachments.add(result, caption, contentWarningReason)
val toUsers = chatroom.users.map { LocalCache.getOrCreateUser(it).toPTag() }
val template =
ChatMessageEvent.build(result.url, toUsers) {
references(listOf(result.url))
if (!caption.isNullOrEmpty()) {
alt(caption)
}
contentWarningReason?.let { contentWarning(it) }
imetas(iMetaAttachments.filterIsIn(setOf(result.url)))
}
account.sendNip17PrivateMessage(template)
}
// ------ // ------
// NIP 04 // NIP 04
// ------ // ------
@@ -74,5 +74,6 @@ fun RoomChatFileUploadDialog(
onCancel, onCancel,
accountViewModel, accountViewModel,
nav, nav,
isNip17 = channelScreenModel.nip17,
) )
} }
@@ -39,12 +39,14 @@ class ChatFileUploader(
suspend fun justUploadNIP17( suspend fun justUploadNIP17(
viewState: ChatFileUploadState, viewState: ChatFileUploadState,
onError: (title: String, message: String) -> Unit, onError: (title: String, message: String) -> Unit,
onEncryptedUploadError: (title: String, message: String) -> Unit,
context: Context, context: Context,
onceUploaded: suspend (List<SuccessfulUploads>) -> Unit, onceUploaded: suspend (List<SuccessfulUploads>) -> Unit,
) { ) {
val orchestrator = viewState.multiOrchestrator ?: return val orchestrator = viewState.multiOrchestrator ?: return
viewState.mediaUploadTracker.startUpload(orchestrator.hasNonMedia()) viewState.mediaUploadTracker.startUpload(orchestrator.hasNonMedia())
if (viewState.encryptFiles) {
val cipher = AESGCM() val cipher = AESGCM()
val results = val results =
@@ -73,6 +75,52 @@ class ChatFileUploader(
} else { } else {
val errorMessages = results.errors.map { stringRes(context, it.errorResource, *it.params) }.distinct() val errorMessages = results.errors.map { stringRes(context, it.errorResource, *it.params) }.distinct()
onEncryptedUploadError(
stringRes(context, R.string.failed_to_upload_encrypted_media_title),
stringRes(context, R.string.failed_to_upload_encrypted_media_message) + "\n\n" + errorMessages.joinToString(".\n"),
)
}
} else {
justUploadNIP17Unencrypted(viewState, onError, context, onceUploaded)
}
viewState.mediaUploadTracker.finishUpload()
}
suspend fun justUploadNIP17Unencrypted(
viewState: ChatFileUploadState,
onError: (title: String, message: String) -> Unit,
context: Context,
onceUploaded: suspend (List<SuccessfulUploads>) -> Unit,
) {
val orchestrator = viewState.multiOrchestrator ?: return
viewState.mediaUploadTracker.startUpload(orchestrator.hasNonMedia())
val results =
orchestrator.upload(
viewState.caption,
viewState.contentWarningReason,
MediaCompressor.intToCompressorQuality(viewState.mediaQualitySlider),
viewState.selectedServer,
account,
context,
)
if (results.allGood) {
val list =
results.successful.mapNotNull { state ->
if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) {
SuccessfulUploads(state.result, viewState.caption, viewState.contentWarningReason, null)
} else {
null
}
}
onceUploaded(list)
viewState.reset()
} else {
val errorMessages = results.errors.map { stringRes(context, it.errorResource, *it.params) }.distinct()
onError(stringRes(context, R.string.failed_to_upload_media_no_details), errorMessages.joinToString(".\n")) onError(stringRes(context, R.string.failed_to_upload_media_no_details), errorMessages.joinToString(".\n"))
} }
@@ -81,6 +81,7 @@ fun ChatFileUploadDialog(
onCancel: () -> Unit, onCancel: () -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
isNip17: Boolean = false,
) { ) {
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@@ -132,7 +133,7 @@ fun ChatFileUploadDialog(
) { ) {
Column(Modifier.fillMaxSize().padding(start = 10.dp, end = 10.dp, bottom = 10.dp)) { Column(Modifier.fillMaxSize().padding(start = 10.dp, end = 10.dp, bottom = 10.dp)) {
Column(Modifier.fillMaxWidth().verticalScroll(scrollState)) { Column(Modifier.fillMaxWidth().verticalScroll(scrollState)) {
ImageVideoPostChat(state, accountViewModel) ImageVideoPostChat(state, accountViewModel, isNip17)
} }
} }
} }
@@ -144,6 +145,7 @@ fun ChatFileUploadDialog(
private fun ImageVideoPostChat( private fun ImageVideoPostChat(
fileUploadState: ChatFileUploadState, fileUploadState: ChatFileUploadState,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
isNip17: Boolean = false,
) { ) {
val fileServers by accountViewModel.account.blossomServers.hostNameFlow val fileServers by accountViewModel.account.blossomServers.hostNameFlow
.collectAsState() .collectAsState()
@@ -190,6 +192,16 @@ private fun ImageVideoPostChat(
onCheckedChange = fileUploadState::updateContentWarning, onCheckedChange = fileUploadState::updateContentWarning,
) )
if (isNip17) {
SettingSwitchItem(
title = R.string.encrypt_files_label,
description = R.string.encrypt_files_description,
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
checked = fileUploadState.encryptFiles,
onCheckedChange = { fileUploadState.encryptFiles = it },
)
}
SettingsRow(R.string.file_server, R.string.file_server_description) { SettingsRow(R.string.file_server, R.string.file_server_description) {
TextSpinner( TextSpinner(
label = "", label = "",
@@ -55,6 +55,8 @@ class ChatFileUploadState(
// 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED // 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED
var mediaQualitySlider by mutableIntStateOf(1) var mediaQualitySlider by mutableIntStateOf(1)
var encryptFiles by mutableStateOf(true)
fun load(uris: ImmutableList<SelectedMedia>) { fun load(uris: ImmutableList<SelectedMedia>) {
reset() reset()
this.multiOrchestrator = MultiOrchestrator(uris) this.multiOrchestrator = MultiOrchestrator(uris)
@@ -70,6 +72,7 @@ class ChatFileUploadState(
mediaUploadTracker.finishUpload() mediaUploadTracker.finishUpload()
caption = "" caption = ""
selectedServer = defaultServer selectedServer = defaultServer
encryptFiles = true
} }
fun deleteMediaToUpload(selected: SelectedMediaProcessing) { fun deleteMediaToUpload(selected: SelectedMediaProcessing) {
@@ -297,8 +297,10 @@ fun ChessLobbyContent(
val userPubkey = accountViewModel.account.userProfile().pubkeyHex val userPubkey = accountViewModel.account.userProfile().pubkeyHex
val hasContent = val hasContent =
activeGames.isNotEmpty() || spectatingGames.isNotEmpty() || activeGames.isNotEmpty() ||
publicGames.isNotEmpty() || challenges.isNotEmpty() spectatingGames.isNotEmpty() ||
publicGames.isNotEmpty() ||
challenges.isNotEmpty()
if (!hasContent) { if (!hasContent) {
// Empty state - use LazyColumn so pull-to-refresh works // Empty state - use LazyColumn so pull-to-refresh works
@@ -83,7 +83,8 @@ class HashtagFeedFilter(
event is PrivateDmEvent || event is PrivateDmEvent ||
event is PollNoteEvent || event is PollNoteEvent ||
event is AudioHeaderEvent event is AudioHeaderEvent
) && event.isTaggedHash(hashTag) ) &&
event.isTaggedHash(hashTag)
fun acceptableViaScope( fun acceptableViaScope(
event: Event?, event: Event?,
@@ -1047,7 +1047,8 @@ open class ShortNotePostViewModel :
( (
!wantsPoll || !wantsPoll ||
( (
pollOptions.isNotEmpty() && pollOptions.all { it.value.label.isNotEmpty() } && pollOptions.isNotEmpty() &&
pollOptions.all { it.value.label.isNotEmpty() } &&
closedAt > TimeUtils.oneMinuteFromNow() closedAt > TimeUtils.oneMinuteFromNow()
) )
) && ) &&
@@ -77,7 +77,8 @@ class UserProfileGalleryFeedFilter(
val noteEvent = it.event val noteEvent = it.event
return ( return (
( (
it.event?.pubKey == user.pubkeyHex && ( it.event?.pubKey == user.pubkeyHex &&
(
noteEvent is PictureEvent || noteEvent is PictureEvent ||
noteEvent is RegularVideoEvent || noteEvent is RegularVideoEvent ||
(noteEvent is ReplaceableVideoEvent && it is AddressableNote) || (noteEvent is ReplaceableVideoEvent && it is AddressableNote) ||
+7
View File
@@ -1216,6 +1216,13 @@
<string name="compression_cancelled">Compression Cancelled</string> <string name="compression_cancelled">Compression Cancelled</string>
<string name="compression_returned_null">Compression failed to return a file</string> <string name="compression_returned_null">Compression failed to return a file</string>
<string name="encrypt_files_label">Encrypt files</string>
<string name="encrypt_files_description">Encrypt files before uploading for privacy. Some servers may not accept encrypted files on free accounts.</string>
<string name="failed_to_upload_encrypted_media_title">Encrypted upload failed</string>
<string name="failed_to_upload_encrypted_media_message">Many servers do not accept encrypted files on free accounts. You can retry without encryption.</string>
<string name="retry_without_encryption">Retry without encryption</string>
<string name="upload_without_encryption_warning">Warning: Without encryption, anyone with the file link can see the content.</string>
<string name="media_compression_quality_label">Media Quality</string> <string name="media_compression_quality_label">Media Quality</string>
<string name="media_compression_quality_explainer">Select Low quality to compress your media to a smaller file with less quality, High quality to compress to a larger file with higher quality or Uncompressed to upload the media without compression.</string> <string name="media_compression_quality_explainer">Select Low quality to compress your media to a smaller file with less quality, High quality to compress to a larger file with higher quality or Uncompressed to upload the media without compression.</string>
<string name="media_compression_quality_low">Low</string> <string name="media_compression_quality_low">Low</string>
@@ -345,8 +345,11 @@ private fun ChessLobby(
listState: LazyListState = rememberLazyListState(), listState: LazyListState = rememberLazyListState(),
) { ) {
val hasContent = val hasContent =
activeGames.isNotEmpty() || spectatingGames.isNotEmpty() || activeGames.isNotEmpty() ||
publicGames.isNotEmpty() || challenges.isNotEmpty() || completedGames.isNotEmpty() spectatingGames.isNotEmpty() ||
publicGames.isNotEmpty() ||
challenges.isNotEmpty() ||
completedGames.isNotEmpty()
if (!hasContent) { if (!hasContent) {
// Empty state // Empty state
@@ -678,7 +678,8 @@ class QueryBuilder(
val search: String? = null, val search: String? = null,
) { ) {
fun isSimpleSearch() = fun isSimpleSearch() =
search != null && search.isNotEmpty() && search != null &&
search.isNotEmpty() &&
(nonDTagsIn == null || nonDTagsIn.isEmpty()) && (nonDTagsIn == null || nonDTagsIn.isEmpty()) &&
(nonDTagsAll == null || nonDTagsAll.isEmpty()) (nonDTagsAll == null || nonDTagsAll.isEmpty())
@@ -301,7 +301,8 @@ class NamecoinNameResolver(
pubkey = rootMatch.content pubkey = rootMatch.content
} }
firstEntry != null && firstEntry.value is JsonPrimitive && firstEntry != null &&
firstEntry.value is JsonPrimitive &&
isValidPubkey((firstEntry.value as JsonPrimitive).content) -> { isValidPubkey((firstEntry.value as JsonPrimitive).content) -> {
resolvedLocalPart = firstEntry.key resolvedLocalPart = firstEntry.key
pubkey = (firstEntry.value as JsonPrimitive).content pubkey = (firstEntry.value as JsonPrimitive).content
@@ -217,9 +217,12 @@ object ChessStateReconstructor {
return fen1 == fen2 // Fallback to exact match return fen1 == fen2 // Fallback to exact match
} }
return parts1[0] == parts2[0] && // Board position return parts1[0] == parts2[0] &&
parts1[1] == parts2[1] && // Active color // Board position
parts1[2] == parts2[2] && // Castling rights parts1[1] == parts2[1] &&
// Active color
parts1[2] == parts2[2] &&
// Castling rights
parts1[3] == parts2[3] // En passant parts1[3] == parts2[3] // En passant
} }
@@ -212,7 +212,8 @@ actual class ChessEngine {
board.getPiece(toSquare) != Piece.NONE || board.getPiece(toSquare) != Piece.NONE ||
( (
pt == com.github.bhlangonijr.chesslib.PieceType.PAWN && pt == com.github.bhlangonijr.chesslib.PieceType.PAWN &&
epTarget != Square.NONE && toSquare == epTarget epTarget != Square.NONE &&
toSquare == epTarget
) )
if (pt != com.github.bhlangonijr.chesslib.PieceType.PAWN) { if (pt != com.github.bhlangonijr.chesslib.PieceType.PAWN) {
@@ -261,7 +261,8 @@ class NamecoinNameResolverTest {
pubkey = rootMatch.content pubkey = rootMatch.content
} }
firstEntry != null && firstEntry.value is kotlinx.serialization.json.JsonPrimitive && firstEntry != null &&
firstEntry.value is kotlinx.serialization.json.JsonPrimitive &&
(firstEntry.value as kotlinx.serialization.json.JsonPrimitive) (firstEntry.value as kotlinx.serialization.json.JsonPrimitive)
.content .content
.matches(Regex("^[0-9a-fA-F]{64}$")) -> { .matches(Regex("^[0-9a-fA-F]{64}$")) -> {