android, desktop: notes to self (#3695)
* android, desktop: notes to self * change api * icon * icon * icon * eol * icon * changes * color * refactor * color * chats size * size --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
parent
c4d75366b5
commit
ec57529f12
@ -186,7 +186,7 @@ struct ContentView: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
requestNtfAuthorization()
|
requestNtfAuthorization()
|
||||||
// Local Authentication notice is to be shown on next start after onboarding is complete
|
// Local Authentication notice is to be shown on next start after onboarding is complete
|
||||||
if (!prefLANoticeShown && prefShowLANotice && !chatModel.chats.isEmpty) {
|
if (!prefLANoticeShown && prefShowLANotice && chatModel.chats.count > 2) {
|
||||||
prefLANoticeShown = true
|
prefLANoticeShown = true
|
||||||
alertManager.showAlert(laNoticeAlert())
|
alertManager.showAlert(laNoticeAlert())
|
||||||
} else if !chatModel.showCallView && CallController.shared.activeCallInvitation == nil {
|
} else if !chatModel.showCallView && CallController.shared.activeCallInvitation == nil {
|
||||||
|
@ -66,7 +66,7 @@ fun MainScreen() {
|
|||||||
!chatModel.controller.appPrefs.laNoticeShown.get()
|
!chatModel.controller.appPrefs.laNoticeShown.get()
|
||||||
&& showAdvertiseLAAlert
|
&& showAdvertiseLAAlert
|
||||||
&& chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete
|
&& chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete
|
||||||
&& chatModel.chats.count() > 1
|
&& chatModel.chats.size > 2
|
||||||
&& chatModel.activeCallInvitation.value == null
|
&& chatModel.activeCallInvitation.value == null
|
||||||
) {
|
) {
|
||||||
AppLock.showLANotice(ChatModel.controller.appPrefs.laNoticeShown) }
|
AppLock.showLANotice(ChatModel.controller.appPrefs.laNoticeShown) }
|
||||||
|
@ -663,6 +663,7 @@ data class ShowingInvitation(
|
|||||||
enum class ChatType(val type: String) {
|
enum class ChatType(val type: String) {
|
||||||
Direct("@"),
|
Direct("@"),
|
||||||
Group("#"),
|
Group("#"),
|
||||||
|
Local("*"),
|
||||||
ContactRequest("<@"),
|
ContactRequest("<@"),
|
||||||
ContactConnection(":");
|
ContactConnection(":");
|
||||||
}
|
}
|
||||||
@ -782,6 +783,7 @@ data class Chat(
|
|||||||
get() = when (chatInfo) {
|
get() = when (chatInfo) {
|
||||||
is ChatInfo.Direct -> true
|
is ChatInfo.Direct -> true
|
||||||
is ChatInfo.Group -> chatInfo.groupInfo.membership.memberRole >= GroupMemberRole.Member
|
is ChatInfo.Group -> chatInfo.groupInfo.membership.memberRole >= GroupMemberRole.Member
|
||||||
|
is ChatInfo.Local -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -864,6 +866,30 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable @SerialName("local")
|
||||||
|
data class Local(val noteFolder: NoteFolder): ChatInfo() {
|
||||||
|
override val chatType get() = ChatType.Local
|
||||||
|
override val localDisplayName get() = noteFolder.localDisplayName
|
||||||
|
override val id get() = noteFolder.id
|
||||||
|
override val apiId get() = noteFolder.apiId
|
||||||
|
override val ready get() = noteFolder.ready
|
||||||
|
override val sendMsgEnabled get() = noteFolder.sendMsgEnabled
|
||||||
|
override val ntfsEnabled get() = noteFolder.ntfsEnabled
|
||||||
|
override val incognito get() = noteFolder.incognito
|
||||||
|
override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature)
|
||||||
|
override val timedMessagesTTL: Int? get() = noteFolder.timedMessagesTTL
|
||||||
|
override val createdAt get() = noteFolder.createdAt
|
||||||
|
override val updatedAt get() = noteFolder.updatedAt
|
||||||
|
override val displayName get() = noteFolder.displayName
|
||||||
|
override val fullName get() = noteFolder.fullName
|
||||||
|
override val image get() = noteFolder.image
|
||||||
|
override val localAlias get() = noteFolder.localAlias
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val sampleData = Local(NoteFolder.sampleData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable @SerialName("contactRequest")
|
@Serializable @SerialName("contactRequest")
|
||||||
class ContactRequest(val contactRequest: UserContactRequest): ChatInfo() {
|
class ContactRequest(val contactRequest: UserContactRequest): ChatInfo() {
|
||||||
override val chatType get() = ChatType.ContactRequest
|
override val chatType get() = ChatType.ContactRequest
|
||||||
@ -1466,6 +1492,40 @@ class MemberSubError (
|
|||||||
val memberError: ChatError
|
val memberError: ChatError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class NoteFolder(
|
||||||
|
val noteFolderId: Long,
|
||||||
|
val favorite: Boolean,
|
||||||
|
val unread: Boolean,
|
||||||
|
override val createdAt: Instant,
|
||||||
|
override val updatedAt: Instant
|
||||||
|
): SomeChat, NamedChat {
|
||||||
|
override val chatType get() = ChatType.Local
|
||||||
|
override val id get() = "*$noteFolderId"
|
||||||
|
override val apiId get() = noteFolderId
|
||||||
|
override val ready get() = true
|
||||||
|
override val sendMsgEnabled get() = true
|
||||||
|
override val ntfsEnabled get() = false
|
||||||
|
override val incognito get() = false
|
||||||
|
override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice
|
||||||
|
override val timedMessagesTTL: Int? get() = null
|
||||||
|
override val displayName get() = generalGetString(MR.strings.note_folder_local_display_name)
|
||||||
|
override val fullName get() = ""
|
||||||
|
override val image get() = null
|
||||||
|
override val localAlias get() = ""
|
||||||
|
override val localDisplayName: String get() = ""
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val sampleData = NoteFolder(
|
||||||
|
noteFolderId = 1,
|
||||||
|
favorite = false,
|
||||||
|
unread = false,
|
||||||
|
createdAt = Clock.System.now(),
|
||||||
|
updatedAt = Clock.System.now()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class UserContactRequest (
|
class UserContactRequest (
|
||||||
val contactRequestId: Long,
|
val contactRequestId: Long,
|
||||||
@ -1666,6 +1726,8 @@ data class ChatItem (
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val localNote: Boolean = chatDir is CIDirection.LocalSnd || chatDir is CIDirection.LocalRcv
|
||||||
|
|
||||||
val isDeletedContent: Boolean get() =
|
val isDeletedContent: Boolean get() =
|
||||||
when (content) {
|
when (content) {
|
||||||
is CIContent.SndDeleted -> true
|
is CIContent.SndDeleted -> true
|
||||||
@ -1933,12 +1995,16 @@ sealed class CIDirection {
|
|||||||
@Serializable @SerialName("directRcv") class DirectRcv: CIDirection()
|
@Serializable @SerialName("directRcv") class DirectRcv: CIDirection()
|
||||||
@Serializable @SerialName("groupSnd") class GroupSnd: CIDirection()
|
@Serializable @SerialName("groupSnd") class GroupSnd: CIDirection()
|
||||||
@Serializable @SerialName("groupRcv") class GroupRcv(val groupMember: GroupMember): CIDirection()
|
@Serializable @SerialName("groupRcv") class GroupRcv(val groupMember: GroupMember): CIDirection()
|
||||||
|
@Serializable @SerialName("localSnd") class LocalSnd: CIDirection()
|
||||||
|
@Serializable @SerialName("localRcv") class LocalRcv: CIDirection()
|
||||||
|
|
||||||
val sent: Boolean get() = when(this) {
|
val sent: Boolean get() = when(this) {
|
||||||
is DirectSnd -> true
|
is DirectSnd -> true
|
||||||
is DirectRcv -> false
|
is DirectRcv -> false
|
||||||
is GroupSnd -> true
|
is GroupSnd -> true
|
||||||
is GroupRcv -> false
|
is GroupRcv -> false
|
||||||
|
is LocalSnd -> true
|
||||||
|
is LocalRcv -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2254,6 +2320,8 @@ class CIQuote (
|
|||||||
is CIDirection.DirectRcv -> null
|
is CIDirection.DirectRcv -> null
|
||||||
is CIDirection.GroupSnd -> membership?.displayName ?: generalGetString(MR.strings.sender_you_pronoun)
|
is CIDirection.GroupSnd -> membership?.displayName ?: generalGetString(MR.strings.sender_you_pronoun)
|
||||||
is CIDirection.GroupRcv -> chatDir.groupMember.displayName
|
is CIDirection.GroupRcv -> chatDir.groupMember.displayName
|
||||||
|
is CIDirection.LocalSnd -> generalGetString(MR.strings.sender_you_pronoun)
|
||||||
|
is CIDirection.LocalRcv -> null
|
||||||
null -> null
|
null -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2525,7 +2593,8 @@ private val rcvCancelAction: CancelAction = CancelAction(
|
|||||||
@Serializable
|
@Serializable
|
||||||
enum class FileProtocol {
|
enum class FileProtocol {
|
||||||
@SerialName("smp") SMP,
|
@SerialName("smp") SMP,
|
||||||
@SerialName("xftp") XFTP;
|
@SerialName("xftp") XFTP,
|
||||||
|
@SerialName("local") LOCAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -680,6 +680,17 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
suspend fun apiCreateChatItem(rh: Long?, noteFolderId: Long, file: CryptoFile? = null, mc: MsgContent): AChatItem? {
|
||||||
|
val cmd = CC.ApiCreateChatItem(noteFolderId, file, mc)
|
||||||
|
val r = sendCmd(rh, cmd)
|
||||||
|
return when (r) {
|
||||||
|
is CR.NewChatItem -> r.chatItem
|
||||||
|
else -> {
|
||||||
|
apiErrorAlert("apiCreateChatItem", generalGetString(MR.strings.error_creating_message), r)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, itemId: Long): ChatItemInfo? {
|
suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, itemId: Long): ChatItemInfo? {
|
||||||
return when (val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, itemId))) {
|
return when (val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, itemId))) {
|
||||||
@ -990,6 +1001,7 @@ object ChatController {
|
|||||||
val titleId = when (type) {
|
val titleId = when (type) {
|
||||||
ChatType.Direct -> MR.strings.error_deleting_contact
|
ChatType.Direct -> MR.strings.error_deleting_contact
|
||||||
ChatType.Group -> MR.strings.error_deleting_group
|
ChatType.Group -> MR.strings.error_deleting_group
|
||||||
|
ChatType.Local -> MR.strings.error_deleting_note_folder
|
||||||
ChatType.ContactRequest -> MR.strings.error_deleting_contact_request
|
ChatType.ContactRequest -> MR.strings.error_deleting_contact_request
|
||||||
ChatType.ContactConnection -> MR.strings.error_deleting_pending_contact_connection
|
ChatType.ContactConnection -> MR.strings.error_deleting_pending_contact_connection
|
||||||
}
|
}
|
||||||
@ -1001,6 +1013,17 @@ object ChatController {
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearChat(chat: Chat, close: (() -> Unit)? = null) {
|
||||||
|
withBGApi {
|
||||||
|
val updatedChatInfo = apiClearChat(chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId)
|
||||||
|
if (updatedChatInfo != null) {
|
||||||
|
chatModel.clearChat(chat.remoteHostId, updatedChatInfo)
|
||||||
|
ntfManager.cancelNotificationsForChat(chat.chatInfo.id)
|
||||||
|
close?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun apiClearChat(rh: Long?, type: ChatType, id: Long): ChatInfo? {
|
suspend fun apiClearChat(rh: Long?, type: ChatType, id: Long): ChatInfo? {
|
||||||
val r = sendCmd(rh, CC.ApiClearChat(type, id))
|
val r = sendCmd(rh, CC.ApiClearChat(type, id))
|
||||||
if (r is CR.ChatCleared) return r.chatInfo
|
if (r is CR.ChatCleared) return r.chatInfo
|
||||||
@ -2243,6 +2266,7 @@ sealed class CC {
|
|||||||
class ApiGetChat(val type: ChatType, val id: Long, val pagination: ChatPagination, val search: String = ""): CC()
|
class ApiGetChat(val type: ChatType, val id: Long, val pagination: ChatPagination, val search: String = ""): CC()
|
||||||
class ApiGetChatItemInfo(val type: ChatType, val id: Long, val itemId: Long): CC()
|
class ApiGetChatItemInfo(val type: ChatType, val id: Long, val itemId: Long): CC()
|
||||||
class ApiSendMessage(val type: ChatType, val id: Long, val file: CryptoFile?, val quotedItemId: Long?, val mc: MsgContent, val live: Boolean, val ttl: Int?): CC()
|
class ApiSendMessage(val type: ChatType, val id: Long, val file: CryptoFile?, val quotedItemId: Long?, val mc: MsgContent, val live: Boolean, val ttl: Int?): CC()
|
||||||
|
class ApiCreateChatItem(val noteFolderId: Long, val file: CryptoFile?, val mc: MsgContent): CC()
|
||||||
class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val mc: MsgContent, val live: Boolean): CC()
|
class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val mc: MsgContent, val live: Boolean): CC()
|
||||||
class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemId: Long, val mode: CIDeleteMode): CC()
|
class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemId: Long, val mode: CIDeleteMode): CC()
|
||||||
class ApiDeleteMemberChatItem(val groupId: Long, val groupMemberId: Long, val itemId: Long): CC()
|
class ApiDeleteMemberChatItem(val groupId: Long, val groupMemberId: Long, val itemId: Long): CC()
|
||||||
@ -2374,6 +2398,9 @@ sealed class CC {
|
|||||||
val ttlStr = if (ttl != null) "$ttl" else "default"
|
val ttlStr = if (ttl != null) "$ttl" else "default"
|
||||||
"/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json ${json.encodeToString(ComposedMessage(file, quotedItemId, mc))}"
|
"/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json ${json.encodeToString(ComposedMessage(file, quotedItemId, mc))}"
|
||||||
}
|
}
|
||||||
|
is ApiCreateChatItem -> {
|
||||||
|
"/_create *$noteFolderId json ${json.encodeToString(ComposedMessage(file, null, mc))}"
|
||||||
|
}
|
||||||
is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${mc.cmdString}"
|
is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${mc.cmdString}"
|
||||||
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} $itemId ${mode.deleteMode}"
|
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} $itemId ${mode.deleteMode}"
|
||||||
is ApiDeleteMemberChatItem -> "/_delete member item #$groupId $groupMemberId $itemId"
|
is ApiDeleteMemberChatItem -> "/_delete member item #$groupId $groupMemberId $itemId"
|
||||||
@ -2502,6 +2529,7 @@ sealed class CC {
|
|||||||
is ApiGetChat -> "apiGetChat"
|
is ApiGetChat -> "apiGetChat"
|
||||||
is ApiGetChatItemInfo -> "apiGetChatItemInfo"
|
is ApiGetChatItemInfo -> "apiGetChatItemInfo"
|
||||||
is ApiSendMessage -> "apiSendMessage"
|
is ApiSendMessage -> "apiSendMessage"
|
||||||
|
is ApiCreateChatItem -> "apiCreateChatItem"
|
||||||
is ApiUpdateChatItem -> "apiUpdateChatItem"
|
is ApiUpdateChatItem -> "apiUpdateChatItem"
|
||||||
is ApiDeleteChatItem -> "apiDeleteChatItem"
|
is ApiDeleteChatItem -> "apiDeleteChatItem"
|
||||||
is ApiDeleteMemberChatItem -> "apiDeleteMemberChatItem"
|
is ApiDeleteMemberChatItem -> "apiDeleteMemberChatItem"
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package chat.simplex.common.ui.theme
|
package chat.simplex.common.ui.theme
|
||||||
|
|
||||||
import androidx.compose.material.LocalContentColor
|
import androidx.compose.material.LocalContentColor
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.graphics.*
|
||||||
|
import chat.simplex.common.views.helpers.mixWith
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
val Purple200 = Color(0xFFBB86FC)
|
val Purple200 = Color(0xFFBB86FC)
|
||||||
val Purple500 = Color(0xFF6200EE)
|
val Purple500 = Color(0xFF6200EE)
|
||||||
@ -27,5 +31,17 @@ val WarningOrange = Color(255, 127, 0, 255)
|
|||||||
val WarningYellow = Color(255, 192, 0, 255)
|
val WarningYellow = Color(255, 192, 0, 255)
|
||||||
val FileLight = Color(183, 190, 199, 255)
|
val FileLight = Color(183, 190, 199, 255)
|
||||||
val FileDark = Color(101, 101, 106, 255)
|
val FileDark = Color(101, 101, 106, 255)
|
||||||
|
val SentMessageColor = Color(0x1E45B8FF)
|
||||||
|
|
||||||
val MenuTextColor: Color @Composable get () = if (isInDarkTheme()) LocalContentColor.current.copy(alpha = 0.8f) else Color.Black
|
val MenuTextColor: Color @Composable get () = if (isInDarkTheme()) LocalContentColor.current.copy(alpha = 0.8f) else Color.Black
|
||||||
|
val NoteFolderIconColor: Color @Composable get() = with(CurrentColors.collectAsState().value.appColors.sentMessage) {
|
||||||
|
// Default color looks too light and better to have it here a little bit brighter
|
||||||
|
if (alpha == SentMessageColor.alpha) {
|
||||||
|
copy(min(SentMessageColor.alpha + 0.1f, 1f))
|
||||||
|
} else {
|
||||||
|
// Color is non-standard and theme maker can choose color without alpha at all since the theme bound to dark/light variant,
|
||||||
|
// and it shouldn't be universal
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ val DarkColorPalette = darkColors(
|
|||||||
)
|
)
|
||||||
val DarkColorPaletteApp = AppColors(
|
val DarkColorPaletteApp = AppColors(
|
||||||
title = SimplexBlue,
|
title = SimplexBlue,
|
||||||
sentMessage = Color(0x1E45B8FF),
|
sentMessage = SentMessageColor,
|
||||||
receivedMessage = Color(0x20B1B0B5)
|
receivedMessage = Color(0x20B1B0B5)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -231,7 +231,7 @@ val LightColorPalette = lightColors(
|
|||||||
)
|
)
|
||||||
val LightColorPaletteApp = AppColors(
|
val LightColorPaletteApp = AppColors(
|
||||||
title = SimplexBlue,
|
title = SimplexBlue,
|
||||||
sentMessage = Color(0x1E45B8FF),
|
sentMessage = SentMessageColor,
|
||||||
receivedMessage = Color(0x20B1B0B5)
|
receivedMessage = Color(0x20B1B0B5)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ val SimplexColorPalette = darkColors(
|
|||||||
)
|
)
|
||||||
val SimplexColorPaletteApp = AppColors(
|
val SimplexColorPaletteApp = AppColors(
|
||||||
title = Color(0xFF267BE5),
|
title = Color(0xFF267BE5),
|
||||||
sentMessage = Color(0x1E45B8FF),
|
sentMessage = SentMessageColor,
|
||||||
receivedMessage = Color(0x20B1B0B5)
|
receivedMessage = Color(0x20B1B0B5)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.common.model.ChatModel.controller
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.usersettings.*
|
import chat.simplex.common.views.usersettings.*
|
||||||
@ -91,7 +92,7 @@ fun ChatInfoView(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteContact = { deleteContactDialog(chat, chatModel, close) },
|
deleteContact = { deleteContactDialog(chat, chatModel, close) },
|
||||||
clearChat = { clearChatDialog(chat, chatModel, close) },
|
clearChat = { clearChatDialog(chat, close) },
|
||||||
switchContactAddress = {
|
switchContactAddress = {
|
||||||
showSwitchAddressAlert(switchAddress = {
|
showSwitchAddressAlert(switchAddress = {
|
||||||
withBGApi {
|
withBGApi {
|
||||||
@ -254,23 +255,22 @@ fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, notify
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearChatDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? = null) {
|
fun clearChatDialog(chat: Chat, close: (() -> Unit)? = null) {
|
||||||
val chatInfo = chat.chatInfo
|
|
||||||
AlertManager.shared.showAlertDialog(
|
AlertManager.shared.showAlertDialog(
|
||||||
title = generalGetString(MR.strings.clear_chat_question),
|
title = generalGetString(MR.strings.clear_chat_question),
|
||||||
text = generalGetString(MR.strings.clear_chat_warning),
|
text = generalGetString(MR.strings.clear_chat_warning),
|
||||||
confirmText = generalGetString(MR.strings.clear_verb),
|
confirmText = generalGetString(MR.strings.clear_verb),
|
||||||
onConfirm = {
|
onConfirm = { controller.clearChat(chat, close) },
|
||||||
withBGApi {
|
destructive = true,
|
||||||
val chatRh = chat.remoteHostId
|
)
|
||||||
val updatedChatInfo = chatModel.controller.apiClearChat(chatRh, chatInfo.chatType, chatInfo.apiId)
|
}
|
||||||
if (updatedChatInfo != null) {
|
|
||||||
chatModel.clearChat(chatRh, updatedChatInfo)
|
fun clearNoteFolderDialog(chat: Chat, close: (() -> Unit)? = null) {
|
||||||
ntfManager.cancelNotificationsForChat(chatInfo.id)
|
AlertManager.shared.showAlertDialog(
|
||||||
close?.invoke()
|
title = generalGetString(MR.strings.clear_note_folder_question),
|
||||||
}
|
text = generalGetString(MR.strings.clear_note_folder_warning),
|
||||||
}
|
confirmText = generalGetString(MR.strings.clear_verb),
|
||||||
},
|
onConfirm = { controller.clearChat(chat, close) },
|
||||||
destructive = true,
|
destructive = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -154,9 +154,9 @@ fun ChatItemInfoView(chatModel: ChatModel, ci: ChatItem, ciInfo: ChatItemInfo, d
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Details() {
|
fun Details() {
|
||||||
AppBarTitle(stringResource(if (sent) MR.strings.sent_message else MR.strings.received_message))
|
AppBarTitle(stringResource(if (ci.localNote) MR.strings.saved_message_title else if (sent) MR.strings.sent_message else MR.strings.received_message))
|
||||||
SectionView {
|
SectionView {
|
||||||
InfoRow(stringResource(MR.strings.info_row_sent_at), localTimestamp(ci.meta.itemTs))
|
InfoRow(stringResource(if (!ci.localNote) MR.strings.info_row_sent_at else MR.strings.info_row_created_at), localTimestamp(ci.meta.itemTs))
|
||||||
if (!sent) {
|
if (!sent) {
|
||||||
InfoRow(stringResource(MR.strings.info_row_received_at), localTimestamp(ci.meta.createdAt))
|
InfoRow(stringResource(MR.strings.info_row_received_at), localTimestamp(ci.meta.createdAt))
|
||||||
}
|
}
|
||||||
@ -393,9 +393,9 @@ private fun membersStatuses(chatModel: ChatModel, memberDeliveryStatuses: List<M
|
|||||||
fun itemInfoShareText(chatModel: ChatModel, ci: ChatItem, chatItemInfo: ChatItemInfo, devTools: Boolean): String {
|
fun itemInfoShareText(chatModel: ChatModel, ci: ChatItem, chatItemInfo: ChatItemInfo, devTools: Boolean): String {
|
||||||
val meta = ci.meta
|
val meta = ci.meta
|
||||||
val sent = ci.chatDir.sent
|
val sent = ci.chatDir.sent
|
||||||
val shareText = mutableListOf<String>("# " + generalGetString(if (sent) MR.strings.sent_message else MR.strings.received_message), "")
|
val shareText = mutableListOf<String>("# " + generalGetString(if (ci.localNote) MR.strings.saved_message_title else if (sent) MR.strings.sent_message else MR.strings.received_message), "")
|
||||||
|
|
||||||
shareText.add(String.format(generalGetString(MR.strings.share_text_sent_at), localTimestamp(meta.itemTs)))
|
shareText.add(String.format(generalGetString(if (ci.localNote) MR.strings.share_text_created_at else MR.strings.share_text_sent_at), localTimestamp(meta.itemTs)))
|
||||||
if (!ci.chatDir.sent) {
|
if (!ci.chatDir.sent) {
|
||||||
shareText.add(String.format(generalGetString(MR.strings.share_text_received_at), localTimestamp(meta.createdAt)))
|
shareText.add(String.format(generalGetString(MR.strings.share_text_received_at), localTimestamp(meta.createdAt)))
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
}
|
}
|
||||||
val clipboard = LocalClipboardManager.current
|
val clipboard = LocalClipboardManager.current
|
||||||
when (chat.chatInfo) {
|
when (chat.chatInfo) {
|
||||||
is ChatInfo.Direct, is ChatInfo.Group -> {
|
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
|
||||||
ChatLayout(
|
ChatLayout(
|
||||||
chat,
|
chat,
|
||||||
unreadCount,
|
unreadCount,
|
||||||
@ -624,11 +624,27 @@ fun ChatInfoToolbar(
|
|||||||
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
|
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
|
||||||
val menuItems = arrayListOf<@Composable () -> Unit>()
|
val menuItems = arrayListOf<@Composable () -> Unit>()
|
||||||
val activeCall by remember { chatModel.activeCall }
|
val activeCall by remember { chatModel.activeCall }
|
||||||
menuItems.add {
|
if (chat.chatInfo is ChatInfo.Local) {
|
||||||
ItemAction(stringResource(MR.strings.search_verb), painterResource(MR.images.ic_search), onClick = {
|
barButtons.add {
|
||||||
showMenu.value = false
|
IconButton({
|
||||||
showSearch = true
|
showMenu.value = false
|
||||||
})
|
showSearch = true
|
||||||
|
}, enabled = chat.chatInfo.noteFolder.ready
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(MR.images.ic_search),
|
||||||
|
stringResource(MR.strings.search_verb).capitalize(Locale.current),
|
||||||
|
tint = if (chat.chatInfo.noteFolder.ready) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
menuItems.add {
|
||||||
|
ItemAction(stringResource(MR.strings.search_verb), painterResource(MR.images.ic_search), onClick = {
|
||||||
|
showMenu.value = false
|
||||||
|
showSearch = true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.allowsFeature(ChatFeature.Calls)) {
|
if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.allowsFeature(ChatFeature.Calls)) {
|
||||||
@ -743,16 +759,18 @@ fun ChatInfoToolbar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
barButtons.add {
|
if (menuItems.isNotEmpty()) {
|
||||||
IconButton({ showMenu.value = true }) {
|
barButtons.add {
|
||||||
Icon(MoreVertFilled, stringResource(MR.strings.icon_descr_more_button), tint = MaterialTheme.colors.primary)
|
IconButton({ showMenu.value = true }) {
|
||||||
|
Icon(MoreVertFilled, stringResource(MR.strings.icon_descr_more_button), tint = MaterialTheme.colors.primary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultTopAppBar(
|
DefaultTopAppBar(
|
||||||
navigationButton = { if (appPlatform.isAndroid || showSearch) { NavigationButtonBack(onBackClicked) } },
|
navigationButton = { if (appPlatform.isAndroid || showSearch) { NavigationButtonBack(onBackClicked) } },
|
||||||
title = { ChatInfoToolbarTitle(chat.chatInfo) },
|
title = { ChatInfoToolbarTitle(chat.chatInfo) },
|
||||||
onTitleClick = info,
|
onTitleClick = if (chat.chatInfo is ChatInfo.Local) null else info,
|
||||||
showSearch = showSearch,
|
showSearch = showSearch,
|
||||||
onSearchValueChanged = onSearchValueChanged,
|
onSearchValueChanged = onSearchValueChanged,
|
||||||
buttons = barButtons
|
buttons = barButtons
|
||||||
@ -910,7 +928,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
if (dismissState.isAnimationRunning && (swipedToStart || swipedToEnd)) {
|
if (dismissState.isAnimationRunning && (swipedToStart || swipedToEnd)) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) {
|
if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chat.chatInfo !is ChatInfo.Local) {
|
||||||
if (composeState.value.editing) {
|
if (composeState.value.editing) {
|
||||||
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
|
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
|
||||||
} else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
} else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
||||||
|
@ -353,7 +353,10 @@ fun ComposeView(
|
|||||||
|
|
||||||
suspend fun send(chat: Chat, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? {
|
suspend fun send(chat: Chat, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? {
|
||||||
val cInfo = chat.chatInfo
|
val cInfo = chat.chatInfo
|
||||||
val aChatItem = chatModel.controller.apiSendMessage(
|
val aChatItem = if (chat.chatInfo.chatType == ChatType.Local)
|
||||||
|
chatModel.controller.apiCreateChatItem(rh = chat.remoteHostId, noteFolderId = chat.chatInfo.apiId, file = file, mc = mc)
|
||||||
|
else
|
||||||
|
chatModel.controller.apiSendMessage(
|
||||||
rh = chat.remoteHostId,
|
rh = chat.remoteHostId,
|
||||||
type = cInfo.chatType,
|
type = cInfo.chatType,
|
||||||
id = cInfo.apiId,
|
id = cInfo.apiId,
|
||||||
@ -877,7 +880,7 @@ fun ComposeView(
|
|||||||
sendMessage(ttl)
|
sendMessage(ttl)
|
||||||
resetLinkPreview()
|
resetLinkPreview()
|
||||||
},
|
},
|
||||||
sendLiveMessage = ::sendLiveMessage,
|
sendLiveMessage = if (chat.chatInfo.chatType != ChatType.Local) ::sendLiveMessage else null,
|
||||||
updateLiveMessage = ::updateLiveMessage,
|
updateLiveMessage = ::updateLiveMessage,
|
||||||
cancelLiveMessage = {
|
cancelLiveMessage = {
|
||||||
composeState.value = composeState.value.copy(liveMessage = null)
|
composeState.value = composeState.value.copy(liveMessage = null)
|
||||||
|
@ -110,7 +110,7 @@ fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLi
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteGroup = { deleteGroupDialog(chat, groupInfo, chatModel, close) },
|
deleteGroup = { deleteGroupDialog(chat, groupInfo, chatModel, close) },
|
||||||
clearChat = { clearChatDialog(chat, chatModel, close) },
|
clearChat = { clearChatDialog(chat, close) },
|
||||||
leaveGroup = { leaveGroupDialog(rhId, groupInfo, chatModel, close) },
|
leaveGroup = { leaveGroupDialog(rhId, groupInfo, chatModel, close) },
|
||||||
manageGroupLink = {
|
manageGroupLink = {
|
||||||
ModalManager.end.showModal { GroupLinkView(chatModel, rhId, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) }
|
ModalManager.end.showModal { GroupLinkView(chatModel, rhId, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) }
|
||||||
|
@ -68,8 +68,8 @@ fun CIFileView(
|
|||||||
|
|
||||||
fun fileAction() {
|
fun fileAction() {
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
when (file.fileStatus) {
|
when {
|
||||||
is CIFileStatus.RcvInvitation -> {
|
file.fileStatus is CIFileStatus.RcvInvitation -> {
|
||||||
if (fileSizeValid()) {
|
if (fileSizeValid()) {
|
||||||
receiveFile(file.fileId)
|
receiveFile(file.fileId)
|
||||||
} else {
|
} else {
|
||||||
@ -79,7 +79,7 @@ fun CIFileView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CIFileStatus.RcvAccepted ->
|
file.fileStatus is CIFileStatus.RcvAccepted ->
|
||||||
when (file.fileProtocol) {
|
when (file.fileProtocol) {
|
||||||
FileProtocol.XFTP ->
|
FileProtocol.XFTP ->
|
||||||
AlertManager.shared.showAlertMsg(
|
AlertManager.shared.showAlertMsg(
|
||||||
@ -91,8 +91,9 @@ fun CIFileView(
|
|||||||
generalGetString(MR.strings.waiting_for_file),
|
generalGetString(MR.strings.waiting_for_file),
|
||||||
generalGetString(MR.strings.file_will_be_received_when_contact_is_online)
|
generalGetString(MR.strings.file_will_be_received_when_contact_is_online)
|
||||||
)
|
)
|
||||||
|
FileProtocol.LOCAL -> {}
|
||||||
}
|
}
|
||||||
is CIFileStatus.RcvComplete -> {
|
file.fileStatus is CIFileStatus.RcvComplete || (file.fileStatus is CIFileStatus.SndStored && file.fileProtocol == FileProtocol.LOCAL) -> {
|
||||||
withBGApi {
|
withBGApi {
|
||||||
var filePath = getLoadedFilePath(file)
|
var filePath = getLoadedFilePath(file)
|
||||||
if (chatModel.connectedToRemote() && filePath == null) {
|
if (chatModel.connectedToRemote() && filePath == null) {
|
||||||
@ -152,11 +153,13 @@ fun CIFileView(
|
|||||||
when (file.fileProtocol) {
|
when (file.fileProtocol) {
|
||||||
FileProtocol.XFTP -> progressIndicator()
|
FileProtocol.XFTP -> progressIndicator()
|
||||||
FileProtocol.SMP -> fileIcon()
|
FileProtocol.SMP -> fileIcon()
|
||||||
|
FileProtocol.LOCAL -> fileIcon()
|
||||||
}
|
}
|
||||||
is CIFileStatus.SndTransfer ->
|
is CIFileStatus.SndTransfer ->
|
||||||
when (file.fileProtocol) {
|
when (file.fileProtocol) {
|
||||||
FileProtocol.XFTP -> progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal)
|
FileProtocol.XFTP -> progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal)
|
||||||
FileProtocol.SMP -> progressIndicator()
|
FileProtocol.SMP -> progressIndicator()
|
||||||
|
FileProtocol.LOCAL -> {}
|
||||||
}
|
}
|
||||||
is CIFileStatus.SndComplete -> fileIcon(innerIcon = painterResource(MR.images.ic_check_filled))
|
is CIFileStatus.SndComplete -> fileIcon(innerIcon = painterResource(MR.images.ic_check_filled))
|
||||||
is CIFileStatus.SndCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close))
|
is CIFileStatus.SndCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close))
|
||||||
|
@ -70,6 +70,7 @@ fun CIImageView(
|
|||||||
when (file.fileProtocol) {
|
when (file.fileProtocol) {
|
||||||
FileProtocol.XFTP -> progressIndicator()
|
FileProtocol.XFTP -> progressIndicator()
|
||||||
FileProtocol.SMP -> {}
|
FileProtocol.SMP -> {}
|
||||||
|
FileProtocol.LOCAL -> {}
|
||||||
}
|
}
|
||||||
is CIFileStatus.SndTransfer -> progressIndicator()
|
is CIFileStatus.SndTransfer -> progressIndicator()
|
||||||
is CIFileStatus.SndComplete -> fileIcon(painterResource(MR.images.ic_check_filled), MR.strings.icon_descr_image_snd_complete)
|
is CIFileStatus.SndComplete -> fileIcon(painterResource(MR.images.ic_check_filled), MR.strings.icon_descr_image_snd_complete)
|
||||||
@ -199,6 +200,7 @@ fun CIImageView(
|
|||||||
generalGetString(MR.strings.waiting_for_image),
|
generalGetString(MR.strings.waiting_for_image),
|
||||||
generalGetString(MR.strings.image_will_be_received_when_contact_is_online)
|
generalGetString(MR.strings.image_will_be_received_when_contact_is_online)
|
||||||
)
|
)
|
||||||
|
FileProtocol.LOCAL -> {}
|
||||||
}
|
}
|
||||||
CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ?
|
CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ?
|
||||||
CIFileStatus.RcvComplete -> {} // ?
|
CIFileStatus.RcvComplete -> {} // ?
|
||||||
|
@ -82,12 +82,12 @@ fun CIVideoView(
|
|||||||
generalGetString(MR.strings.waiting_for_video),
|
generalGetString(MR.strings.waiting_for_video),
|
||||||
generalGetString(MR.strings.video_will_be_received_when_contact_completes_uploading)
|
generalGetString(MR.strings.video_will_be_received_when_contact_completes_uploading)
|
||||||
)
|
)
|
||||||
|
|
||||||
FileProtocol.SMP ->
|
FileProtocol.SMP ->
|
||||||
AlertManager.shared.showAlertMsg(
|
AlertManager.shared.showAlertMsg(
|
||||||
generalGetString(MR.strings.waiting_for_video),
|
generalGetString(MR.strings.waiting_for_video),
|
||||||
generalGetString(MR.strings.video_will_be_received_when_contact_is_online)
|
generalGetString(MR.strings.video_will_be_received_when_contact_is_online)
|
||||||
)
|
)
|
||||||
|
FileProtocol.LOCAL -> {}
|
||||||
}
|
}
|
||||||
CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ?
|
CIFileStatus.RcvTransfer(rcvProgress = 7, rcvTotal = 10) -> {} // ?
|
||||||
CIFileStatus.RcvComplete -> {} // ?
|
CIFileStatus.RcvComplete -> {} // ?
|
||||||
@ -377,11 +377,13 @@ private fun loadingIndicator(file: CIFile?) {
|
|||||||
when (file.fileProtocol) {
|
when (file.fileProtocol) {
|
||||||
FileProtocol.XFTP -> progressIndicator()
|
FileProtocol.XFTP -> progressIndicator()
|
||||||
FileProtocol.SMP -> {}
|
FileProtocol.SMP -> {}
|
||||||
|
FileProtocol.LOCAL -> {}
|
||||||
}
|
}
|
||||||
is CIFileStatus.SndTransfer ->
|
is CIFileStatus.SndTransfer ->
|
||||||
when (file.fileProtocol) {
|
when (file.fileProtocol) {
|
||||||
FileProtocol.XFTP -> progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal)
|
FileProtocol.XFTP -> progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal)
|
||||||
FileProtocol.SMP -> progressIndicator()
|
FileProtocol.SMP -> progressIndicator()
|
||||||
|
FileProtocol.LOCAL -> {}
|
||||||
}
|
}
|
||||||
is CIFileStatus.SndComplete -> fileIcon(painterResource(MR.images.ic_check_filled), MR.strings.icon_descr_video_snd_complete)
|
is CIFileStatus.SndComplete -> fileIcon(painterResource(MR.images.ic_check_filled), MR.strings.icon_descr_video_snd_complete)
|
||||||
is CIFileStatus.SndCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
|
is CIFileStatus.SndCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
|
||||||
|
@ -183,7 +183,7 @@ fun ChatItemView(
|
|||||||
if (cInfo.featureEnabled(ChatFeature.Reactions) && cItem.allowAddReaction) {
|
if (cInfo.featureEnabled(ChatFeature.Reactions) && cItem.allowAddReaction) {
|
||||||
MsgReactionsMenu()
|
MsgReactionsMenu()
|
||||||
}
|
}
|
||||||
if (cItem.meta.itemDeleted == null && !live) {
|
if (cItem.meta.itemDeleted == null && !live && !cItem.localNote) {
|
||||||
ItemAction(stringResource(MR.strings.reply_verb), painterResource(MR.images.ic_reply), onClick = {
|
ItemAction(stringResource(MR.strings.reply_verb), painterResource(MR.images.ic_reply), onClick = {
|
||||||
if (composeState.value.editing) {
|
if (composeState.value.editing) {
|
||||||
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
|
composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
|
||||||
@ -240,7 +240,7 @@ fun ChatItemView(
|
|||||||
if (revealed.value) {
|
if (revealed.value) {
|
||||||
HideItemAction(revealed, showMenu)
|
HideItemAction(revealed, showMenu)
|
||||||
}
|
}
|
||||||
if (cItem.meta.itemDeleted == null && cItem.file != null && cItem.file.cancelAction != null) {
|
if (cItem.meta.itemDeleted == null && cItem.file != null && cItem.file.cancelAction != null && !cItem.localNote) {
|
||||||
CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cItem.file.cancelAction)
|
CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cItem.file.cancelAction)
|
||||||
}
|
}
|
||||||
if (!(live && cItem.meta.isLive)) {
|
if (!(live && cItem.meta.isLive)) {
|
||||||
@ -677,7 +677,7 @@ fun deleteMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMes
|
|||||||
deleteMessage(chatItem.id, CIDeleteMode.cidmInternal)
|
deleteMessage(chatItem.id, CIDeleteMode.cidmInternal)
|
||||||
AlertManager.shared.hideAlert()
|
AlertManager.shared.hideAlert()
|
||||||
}) { Text(stringResource(MR.strings.for_me_only), color = MaterialTheme.colors.error) }
|
}) { Text(stringResource(MR.strings.for_me_only), color = MaterialTheme.colors.error) }
|
||||||
if (chatItem.meta.editable) {
|
if (chatItem.meta.editable && !chatItem.localNote) {
|
||||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast)
|
deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast)
|
||||||
|
@ -95,6 +95,25 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
|
|||||||
selectedChat,
|
selectedChat,
|
||||||
nextChatSelected,
|
nextChatSelected,
|
||||||
)
|
)
|
||||||
|
is ChatInfo.Local -> {
|
||||||
|
ChatListNavLinkLayout(
|
||||||
|
chatLinkPreview = {
|
||||||
|
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
|
||||||
|
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, disabled, linkMode, inProgress = false, progressByTimeout = false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
click = { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) },
|
||||||
|
dropdownMenuItems = {
|
||||||
|
tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) {
|
||||||
|
NoteFolderMenuItems(chat, showMenu, showMarkRead)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showMenu,
|
||||||
|
disabled,
|
||||||
|
selectedChat,
|
||||||
|
nextChatSelected,
|
||||||
|
)
|
||||||
|
}
|
||||||
is ChatInfo.ContactRequest ->
|
is ChatInfo.ContactRequest ->
|
||||||
ChatListNavLinkLayout(
|
ChatListNavLinkLayout(
|
||||||
chatLinkPreview = {
|
chatLinkPreview = {
|
||||||
@ -174,6 +193,10 @@ fun groupChatAction(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, inP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun noteFolderChatAction(rhId: Long?, noteFolder: NoteFolder) {
|
||||||
|
withBGApi { openChat(rhId, ChatInfo.Local(noteFolder), chatModel) }
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun openDirectChat(rhId: Long?, contactId: Long, chatModel: ChatModel) {
|
suspend fun openDirectChat(rhId: Long?, contactId: Long, chatModel: ChatModel) {
|
||||||
val chat = chatModel.controller.apiGetChat(rhId, ChatType.Direct, contactId)
|
val chat = chatModel.controller.apiGetChat(rhId, ChatType.Direct, contactId)
|
||||||
if (chat != null) {
|
if (chat != null) {
|
||||||
@ -247,7 +270,7 @@ fun ContactMenuItems(chat: Chat, contact: Contact, chatModel: ChatModel, showMen
|
|||||||
}
|
}
|
||||||
ToggleFavoritesChatAction(chat, chatModel, chat.chatInfo.chatSettings?.favorite == true, showMenu)
|
ToggleFavoritesChatAction(chat, chatModel, chat.chatInfo.chatSettings?.favorite == true, showMenu)
|
||||||
ToggleNotificationsChatAction(chat, chatModel, chat.chatInfo.ntfsEnabled, showMenu)
|
ToggleNotificationsChatAction(chat, chatModel, chat.chatInfo.ntfsEnabled, showMenu)
|
||||||
ClearChatAction(chat, chatModel, showMenu)
|
ClearChatAction(chat, showMenu)
|
||||||
}
|
}
|
||||||
DeleteContactAction(chat, chatModel, showMenu)
|
DeleteContactAction(chat, chatModel, showMenu)
|
||||||
}
|
}
|
||||||
@ -286,7 +309,7 @@ fun GroupMenuItems(
|
|||||||
}
|
}
|
||||||
ToggleFavoritesChatAction(chat, chatModel, chat.chatInfo.chatSettings?.favorite == true, showMenu)
|
ToggleFavoritesChatAction(chat, chatModel, chat.chatInfo.chatSettings?.favorite == true, showMenu)
|
||||||
ToggleNotificationsChatAction(chat, chatModel, chat.chatInfo.ntfsEnabled, showMenu)
|
ToggleNotificationsChatAction(chat, chatModel, chat.chatInfo.ntfsEnabled, showMenu)
|
||||||
ClearChatAction(chat, chatModel, showMenu)
|
ClearChatAction(chat, showMenu)
|
||||||
if (groupInfo.membership.memberCurrent) {
|
if (groupInfo.membership.memberCurrent) {
|
||||||
LeaveGroupAction(chat.remoteHostId, groupInfo, chatModel, showMenu)
|
LeaveGroupAction(chat.remoteHostId, groupInfo, chatModel, showMenu)
|
||||||
}
|
}
|
||||||
@ -297,6 +320,16 @@ fun GroupMenuItems(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NoteFolderMenuItems(chat: Chat, showMenu: MutableState<Boolean>, showMarkRead: Boolean) {
|
||||||
|
if (showMarkRead) {
|
||||||
|
MarkReadChatAction(chat, chatModel, showMenu)
|
||||||
|
} else {
|
||||||
|
MarkUnreadChatAction(chat, chatModel, showMenu)
|
||||||
|
}
|
||||||
|
ClearNoteFolderAction(chat, showMenu)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MarkReadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
|
fun MarkReadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
|
||||||
ItemAction(
|
ItemAction(
|
||||||
@ -347,12 +380,25 @@ fun ToggleNotificationsChatAction(chat: Chat, chatModel: ChatModel, ntfsEnabled:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ClearChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
|
fun ClearChatAction(chat: Chat, showMenu: MutableState<Boolean>) {
|
||||||
ItemAction(
|
ItemAction(
|
||||||
stringResource(MR.strings.clear_chat_menu_action),
|
stringResource(MR.strings.clear_chat_menu_action),
|
||||||
painterResource(MR.images.ic_settings_backup_restore),
|
painterResource(MR.images.ic_settings_backup_restore),
|
||||||
onClick = {
|
onClick = {
|
||||||
clearChatDialog(chat, chatModel)
|
clearChatDialog(chat)
|
||||||
|
showMenu.value = false
|
||||||
|
},
|
||||||
|
color = WarningOrange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ClearNoteFolderAction(chat: Chat, showMenu: MutableState<Boolean>) {
|
||||||
|
ItemAction(
|
||||||
|
stringResource(MR.strings.clear_chat_menu_action),
|
||||||
|
painterResource(MR.images.ic_settings_backup_restore),
|
||||||
|
onClick = {
|
||||||
|
clearNoteFolderDialog(chat)
|
||||||
showMenu.value = false
|
showMenu.value = false
|
||||||
},
|
},
|
||||||
color = WarningOrange
|
color = WarningOrange
|
||||||
|
@ -518,6 +518,7 @@ private fun filteredChats(
|
|||||||
} else {
|
} else {
|
||||||
viewNameContains(cInfo, s)
|
viewNameContains(cInfo, s)
|
||||||
}
|
}
|
||||||
|
is ChatInfo.Local -> s.isEmpty() || viewNameContains(cInfo, s)
|
||||||
is ChatInfo.ContactRequest -> s.isEmpty() || viewNameContains(cInfo, s)
|
is ChatInfo.ContactRequest -> s.isEmpty() || viewNameContains(cInfo, s)
|
||||||
is ChatInfo.ContactConnection -> (s.isNotEmpty() && cInfo.contactConnection.localAlias.lowercase().contains(s)) || (s.isEmpty() && chat.id == chatModel.chatId.value)
|
is ChatInfo.ContactConnection -> (s.isNotEmpty() && cInfo.contactConnection.localAlias.lowercase().contains(s)) || (s.isEmpty() && chat.id == chatModel.chatId.value)
|
||||||
is ChatInfo.InvalidJSON -> chat.id == chatModel.chatId.value
|
is ChatInfo.InvalidJSON -> chat.id == chatModel.chatId.value
|
||||||
|
@ -9,9 +9,10 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.ui.theme.Indigo
|
|
||||||
import chat.simplex.common.views.helpers.ProfileImage
|
import chat.simplex.common.views.helpers.ProfileImage
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||||
@ -29,6 +30,12 @@ fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
|||||||
click = { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel) },
|
click = { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel) },
|
||||||
stopped
|
stopped
|
||||||
)
|
)
|
||||||
|
is ChatInfo.Local ->
|
||||||
|
ShareListNavLinkLayout(
|
||||||
|
chatLinkPreview = { SharePreviewView(chat) },
|
||||||
|
click = { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) },
|
||||||
|
stopped
|
||||||
|
)
|
||||||
is ChatInfo.ContactRequest, is ChatInfo.ContactConnection, is ChatInfo.InvalidJSON -> {}
|
is ChatInfo.ContactRequest, is ChatInfo.ContactConnection, is ChatInfo.InvalidJSON -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,7 +63,11 @@ private fun SharePreviewView(chat: Chat) {
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
ProfileImage(size = 46.dp, chat.chatInfo.image)
|
if (chat.chatInfo is ChatInfo.Local) {
|
||||||
|
ProfileImage(size = 46.dp, null, icon = MR.images.ic_folder_filled, color = NoteFolderIconColor)
|
||||||
|
} else {
|
||||||
|
ProfileImage(size = 46.dp, chat.chatInfo.image)
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
|
chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||||
color = if (chat.chatInfo.incognito) Indigo else Color.Unspecified
|
color = if (chat.chatInfo.incognito) Indigo else Color.Unspecified
|
||||||
|
@ -17,6 +17,7 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.model.ChatInfo
|
import chat.simplex.common.model.ChatInfo
|
||||||
import chat.simplex.common.platform.base64ToBitmap
|
import chat.simplex.common.platform.base64ToBitmap
|
||||||
|
import chat.simplex.common.ui.theme.NoteFolderIconColor
|
||||||
import chat.simplex.common.ui.theme.SimpleXTheme
|
import chat.simplex.common.ui.theme.SimpleXTheme
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.ImageResource
|
import dev.icerock.moko.resources.ImageResource
|
||||||
@ -24,9 +25,12 @@ import dev.icerock.moko.resources.ImageResource
|
|||||||
@Composable
|
@Composable
|
||||||
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) {
|
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) {
|
||||||
val icon =
|
val icon =
|
||||||
if (chatInfo is ChatInfo.Group) MR.images.ic_supervised_user_circle_filled
|
when (chatInfo) {
|
||||||
else MR.images.ic_account_circle_filled
|
is ChatInfo.Group -> MR.images.ic_supervised_user_circle_filled
|
||||||
ProfileImage(size, chatInfo.image, icon, iconColor)
|
is ChatInfo.Direct -> MR.images.ic_account_circle_filled
|
||||||
|
else -> MR.images.ic_folder_filled
|
||||||
|
}
|
||||||
|
ProfileImage(size, chatInfo.image, icon, if (chatInfo is ChatInfo.Local) NoteFolderIconColor else iconColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -132,6 +132,8 @@ const val MAX_FILE_SIZE_SMP: Long = 8000000
|
|||||||
|
|
||||||
const val MAX_FILE_SIZE_XFTP: Long = 1_073_741_824 // 1GB
|
const val MAX_FILE_SIZE_XFTP: Long = 1_073_741_824 // 1GB
|
||||||
|
|
||||||
|
const val MAX_FILE_SIZE_LOCAL: Long = Long.MAX_VALUE
|
||||||
|
|
||||||
expect fun getAppFileUri(fileName: String): URI
|
expect fun getAppFileUri(fileName: String): URI
|
||||||
|
|
||||||
// https://developer.android.com/training/data-storage/shared/documents-files#bitmap
|
// https://developer.android.com/training/data-storage/shared/documents-files#bitmap
|
||||||
@ -357,6 +359,7 @@ fun getMaxFileSize(fileProtocol: FileProtocol): Long {
|
|||||||
return when (fileProtocol) {
|
return when (fileProtocol) {
|
||||||
FileProtocol.XFTP -> MAX_FILE_SIZE_XFTP
|
FileProtocol.XFTP -> MAX_FILE_SIZE_XFTP
|
||||||
FileProtocol.SMP -> MAX_FILE_SIZE_SMP
|
FileProtocol.SMP -> MAX_FILE_SIZE_SMP
|
||||||
|
FileProtocol.LOCAL -> MAX_FILE_SIZE_LOCAL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,9 @@
|
|||||||
<string name="decryption_error">Decryption error</string>
|
<string name="decryption_error">Decryption error</string>
|
||||||
<string name="encryption_renegotiation_error">Encryption re-negotiation error</string>
|
<string name="encryption_renegotiation_error">Encryption re-negotiation error</string>
|
||||||
|
|
||||||
|
<!-- NoteFolder - ChatModel.kt -->
|
||||||
|
<string name="note_folder_local_display_name">Private notes</string>
|
||||||
|
|
||||||
<!-- PendingContactConnection - ChatModel.kt -->
|
<!-- PendingContactConnection - ChatModel.kt -->
|
||||||
<string name="connection_local_display_name">connection %1$d</string>
|
<string name="connection_local_display_name">connection %1$d</string>
|
||||||
<string name="display_name_connection_established">connection established</string>
|
<string name="display_name_connection_established">connection established</string>
|
||||||
@ -99,6 +102,7 @@
|
|||||||
<string name="connection_error">Connection error</string>
|
<string name="connection_error">Connection error</string>
|
||||||
<string name="network_error_desc">Please check your network connection with %1$s and try again.</string>
|
<string name="network_error_desc">Please check your network connection with %1$s and try again.</string>
|
||||||
<string name="error_sending_message">Error sending message</string>
|
<string name="error_sending_message">Error sending message</string>
|
||||||
|
<string name="error_creating_message">Error creating message</string>
|
||||||
<string name="error_loading_details">Error loading details</string>
|
<string name="error_loading_details">Error loading details</string>
|
||||||
<string name="error_adding_members">Error adding member(s)</string>
|
<string name="error_adding_members">Error adding member(s)</string>
|
||||||
<string name="error_joining_group">Error joining group</string>
|
<string name="error_joining_group">Error joining group</string>
|
||||||
@ -116,6 +120,7 @@
|
|||||||
<string name="sender_may_have_deleted_the_connection_request">Sender may have deleted the connection request.</string>
|
<string name="sender_may_have_deleted_the_connection_request">Sender may have deleted the connection request.</string>
|
||||||
<string name="error_deleting_contact">Error deleting contact</string>
|
<string name="error_deleting_contact">Error deleting contact</string>
|
||||||
<string name="error_deleting_group">Error deleting group</string>
|
<string name="error_deleting_group">Error deleting group</string>
|
||||||
|
<string name="error_deleting_note_folder">Error deleting private notes</string>
|
||||||
<string name="error_deleting_contact_request">Error deleting contact request</string>
|
<string name="error_deleting_contact_request">Error deleting contact request</string>
|
||||||
<string name="error_deleting_pending_contact_connection">Error deleting pending contact connection</string>
|
<string name="error_deleting_pending_contact_connection">Error deleting pending contact connection</string>
|
||||||
<string name="error_changing_address">Error changing address</string>
|
<string name="error_changing_address">Error changing address</string>
|
||||||
@ -471,7 +476,9 @@
|
|||||||
|
|
||||||
<!-- Clear Chat - ChatListNavLinkView.kt -->
|
<!-- Clear Chat - ChatListNavLinkView.kt -->
|
||||||
<string name="clear_chat_question">Clear chat?</string>
|
<string name="clear_chat_question">Clear chat?</string>
|
||||||
|
<string name="clear_note_folder_question">Clear private notes?</string>
|
||||||
<string name="clear_chat_warning">All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</string>
|
<string name="clear_chat_warning">All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</string>
|
||||||
|
<string name="clear_note_folder_warning">All messages will be deleted - this cannot be undone!</string>
|
||||||
<string name="clear_verb">Clear</string>
|
<string name="clear_verb">Clear</string>
|
||||||
<string name="clear_chat_button">Clear chat</string>
|
<string name="clear_chat_button">Clear chat</string>
|
||||||
<string name="clear_chat_menu_action">Clear</string>
|
<string name="clear_chat_menu_action">Clear</string>
|
||||||
@ -1301,6 +1308,7 @@
|
|||||||
<string name="info_row_database_id">Database ID</string>
|
<string name="info_row_database_id">Database ID</string>
|
||||||
<string name="info_row_updated_at">Record updated at</string>
|
<string name="info_row_updated_at">Record updated at</string>
|
||||||
<string name="info_row_sent_at">Sent at</string>
|
<string name="info_row_sent_at">Sent at</string>
|
||||||
|
<string name="info_row_created_at">Created at</string>
|
||||||
<string name="info_row_received_at">Received at</string>
|
<string name="info_row_received_at">Received at</string>
|
||||||
<string name="info_row_deleted_at">Deleted at</string>
|
<string name="info_row_deleted_at">Deleted at</string>
|
||||||
<string name="info_row_moderated_at">Moderated at</string>
|
<string name="info_row_moderated_at">Moderated at</string>
|
||||||
@ -1308,6 +1316,7 @@
|
|||||||
<string name="share_text_database_id">Database ID: %d</string>
|
<string name="share_text_database_id">Database ID: %d</string>
|
||||||
<string name="share_text_updated_at">Record updated at: %s</string>
|
<string name="share_text_updated_at">Record updated at: %s</string>
|
||||||
<string name="share_text_sent_at">Sent at: %s</string>
|
<string name="share_text_sent_at">Sent at: %s</string>
|
||||||
|
<string name="share_text_created_at">Created at: %s</string>
|
||||||
<string name="share_text_received_at">Received at: %s</string>
|
<string name="share_text_received_at">Received at: %s</string>
|
||||||
<string name="share_text_deleted_at">Deleted at: %s</string>
|
<string name="share_text_deleted_at">Deleted at: %s</string>
|
||||||
<string name="share_text_moderated_at">Moderated at: %s</string>
|
<string name="share_text_moderated_at">Moderated at: %s</string>
|
||||||
@ -1317,6 +1326,7 @@
|
|||||||
<string name="current_version_timestamp">%s (current)</string>
|
<string name="current_version_timestamp">%s (current)</string>
|
||||||
<string name="item_info_no_text">no text</string>
|
<string name="item_info_no_text">no text</string>
|
||||||
<string name="recipient_colon_delivery_status">%s: %s</string>
|
<string name="recipient_colon_delivery_status">%s: %s</string>
|
||||||
|
<string name="saved_message_title">Saved message</string>
|
||||||
|
|
||||||
<!-- GroupMemberInfoView.kt -->
|
<!-- GroupMemberInfoView.kt -->
|
||||||
<string name="button_remove_member_question">Remove member?</string>
|
<string name="button_remove_member_question">Remove member?</string>
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="24"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
id="path151"
|
||||||
|
style="display:inline;fill:#ffffff;stroke-width:58"
|
||||||
|
d="M 480,-880 A 400,400 0 0 0 80,-480 400,400 0 0 0 480,-80 400,400 0 0 0 880,-480 400,400 0 0 0 480,-880 Z m -194.65461,218.29769 h 162.0477 l 33.18257,33.18257 h 194.07895 c 8.44741,0 16.07203,3.40719 22.90296,10.23849 6.83129,6.83091 10.2796,14.49667 10.2796,22.94408 v 263.85691 c 0,8.44742 -3.44831,16.07205 -10.2796,22.90295 -6.83093,6.83132 -14.45555,10.27962 -22.90296,10.27962 H 285.34539 c -8.83193,0 -16.59324,-3.4483 -23.23191,-10.27962 -6.63903,-6.8309 -9.95065,-14.45553 -9.95065,-22.90295 v -297.03948 c 0,-8.44742 3.31162,-16.07205 9.95065,-22.90295 6.63867,-6.83132 14.39998,-10.27962 23.23191,-10.27962 z" />
|
||||||
|
<rect
|
||||||
|
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:19.9532;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
|
||||||
|
id="rect151"
|
||||||
|
width="500"
|
||||||
|
height="30"
|
||||||
|
x="220"
|
||||||
|
y="-540" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
Loading…
Reference in New Issue
Block a user