diff --git a/app/schemas/one.mixin.android.db.MixinDatabase/71.json b/app/schemas/one.mixin.android.db.MixinDatabase/71.json new file mode 100644 index 0000000000..00795ece38 --- /dev/null +++ b/app/schemas/one.mixin.android.db.MixinDatabase/71.json @@ -0,0 +1,3351 @@ +{ + "formatVersion": 1, + "database": { + "version": 71, + "identityHash": "61cb9345a060036bd9618e3986544805", + "entities": [ + { + "tableName": "users", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `identity_number` TEXT NOT NULL, `relationship` TEXT NOT NULL, `biography` TEXT NOT NULL, `full_name` TEXT, `avatar_url` TEXT, `phone` TEXT, `is_verified` INTEGER, `created_at` TEXT, `mute_until` TEXT, `has_pin` INTEGER, `app_id` TEXT, `is_scam` INTEGER, `is_deactivated` INTEGER, `membership` TEXT, PRIMARY KEY(`user_id`))", + "fields": [ + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "identityNumber", + "columnName": "identity_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "relationship", + "columnName": "relationship", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "biography", + "columnName": "biography", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarUrl", + "columnName": "avatar_url", + "affinity": "TEXT" + }, + { + "fieldPath": "phone", + "columnName": "phone", + "affinity": "TEXT" + }, + { + "fieldPath": "isVerified", + "columnName": "is_verified", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT" + }, + { + "fieldPath": "muteUntil", + "columnName": "mute_until", + "affinity": "TEXT" + }, + { + "fieldPath": "hasPin", + "columnName": "has_pin", + "affinity": "INTEGER" + }, + { + "fieldPath": "appId", + "columnName": "app_id", + "affinity": "TEXT" + }, + { + "fieldPath": "isScam", + "columnName": "is_scam", + "affinity": "INTEGER" + }, + { + "fieldPath": "isDeactivated", + "columnName": "is_deactivated", + "affinity": "INTEGER" + }, + { + "fieldPath": "membership", + "columnName": "membership", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "user_id" + ] + }, + "indices": [ + { + "name": "index_users_relationship_full_name", + "unique": false, + "columnNames": [ + "relationship", + "full_name" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_users_relationship_full_name` ON `${TABLE_NAME}` (`relationship`, `full_name`)" + } + ] + }, + { + "tableName": "conversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `owner_id` TEXT, `category` TEXT, `name` TEXT, `icon_url` TEXT, `announcement` TEXT, `code_url` TEXT, `pay_type` TEXT, `created_at` TEXT NOT NULL, `pin_time` TEXT, `last_message_id` TEXT, `last_read_message_id` TEXT, `unseen_message_count` INTEGER, `status` INTEGER NOT NULL, `draft` TEXT, `mute_until` TEXT, `last_message_created_at` TEXT, `expire_in` INTEGER, PRIMARY KEY(`conversation_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ownerId", + "columnName": "owner_id", + "affinity": "TEXT" + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT" + }, + { + "fieldPath": "announcement", + "columnName": "announcement", + "affinity": "TEXT" + }, + { + "fieldPath": "codeUrl", + "columnName": "code_url", + "affinity": "TEXT" + }, + { + "fieldPath": "payType", + "columnName": "pay_type", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pinTime", + "columnName": "pin_time", + "affinity": "TEXT" + }, + { + "fieldPath": "lastMessageId", + "columnName": "last_message_id", + "affinity": "TEXT" + }, + { + "fieldPath": "lastReadMessageId", + "columnName": "last_read_message_id", + "affinity": "TEXT" + }, + { + "fieldPath": "unseenMessageCount", + "columnName": "unseen_message_count", + "affinity": "INTEGER" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "draft", + "columnName": "draft", + "affinity": "TEXT" + }, + { + "fieldPath": "muteUntil", + "columnName": "mute_until", + "affinity": "TEXT" + }, + { + "fieldPath": "lastMessageCreatedAt", + "columnName": "last_message_created_at", + "affinity": "TEXT" + }, + { + "fieldPath": "expireIn", + "columnName": "expire_in", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id" + ] + }, + "indices": [ + { + "name": "index_conversations_pin_time_last_message_created_at", + "unique": false, + "columnNames": [ + "pin_time", + "last_message_created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_conversations_pin_time_last_message_created_at` ON `${TABLE_NAME}` (`pin_time`, `last_message_created_at`)" + } + ] + }, + { + "tableName": "messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `category` TEXT NOT NULL, `content` TEXT, `media_url` TEXT, `media_mime_type` TEXT, `media_size` INTEGER, `media_duration` TEXT, `media_width` INTEGER, `media_height` INTEGER, `media_hash` TEXT, `thumb_image` TEXT, `thumb_url` TEXT, `media_key` BLOB, `media_digest` BLOB, `media_status` TEXT, `status` TEXT NOT NULL, `created_at` TEXT NOT NULL, `action` TEXT, `participant_id` TEXT, `snapshot_id` TEXT, `hyperlink` TEXT, `name` TEXT, `album_id` TEXT, `sticker_id` TEXT, `shared_user_id` TEXT, `media_waveform` BLOB, `media_mine_type` TEXT, `quote_message_id` TEXT, `quote_content` TEXT, `caption` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`conversation_id`) REFERENCES `conversations`(`conversation_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaUrl", + "columnName": "media_url", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaMimeType", + "columnName": "media_mime_type", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaSize", + "columnName": "media_size", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaDuration", + "columnName": "media_duration", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaWidth", + "columnName": "media_width", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaHeight", + "columnName": "media_height", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaHash", + "columnName": "media_hash", + "affinity": "TEXT" + }, + { + "fieldPath": "thumbImage", + "columnName": "thumb_image", + "affinity": "TEXT" + }, + { + "fieldPath": "thumbUrl", + "columnName": "thumb_url", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaKey", + "columnName": "media_key", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaDigest", + "columnName": "media_digest", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaStatus", + "columnName": "media_status", + "affinity": "TEXT" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "action", + "columnName": "action", + "affinity": "TEXT" + }, + { + "fieldPath": "participantId", + "columnName": "participant_id", + "affinity": "TEXT" + }, + { + "fieldPath": "snapshotId", + "columnName": "snapshot_id", + "affinity": "TEXT" + }, + { + "fieldPath": "hyperlink", + "columnName": "hyperlink", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT" + }, + { + "fieldPath": "stickerId", + "columnName": "sticker_id", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedUserId", + "columnName": "shared_user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaWaveform", + "columnName": "media_waveform", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaMineType", + "columnName": "media_mine_type", + "affinity": "TEXT" + }, + { + "fieldPath": "quoteMessageId", + "columnName": "quote_message_id", + "affinity": "TEXT" + }, + { + "fieldPath": "quoteContent", + "columnName": "quote_content", + "affinity": "TEXT" + }, + { + "fieldPath": "caption", + "columnName": "caption", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_messages_conversation_id_created_at", + "unique": false, + "columnNames": [ + "conversation_id", + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation_id_created_at` ON `${TABLE_NAME}` (`conversation_id`, `created_at`)" + }, + { + "name": "index_messages_conversation_id_category", + "unique": false, + "columnNames": [ + "conversation_id", + "category" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation_id_category` ON `${TABLE_NAME}` (`conversation_id`, `category`)" + }, + { + "name": "index_messages_conversation_id_quote_message_id", + "unique": false, + "columnNames": [ + "conversation_id", + "quote_message_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation_id_quote_message_id` ON `${TABLE_NAME}` (`conversation_id`, `quote_message_id`)" + }, + { + "name": "index_messages_conversation_id_status_user_id_created_at", + "unique": false, + "columnNames": [ + "conversation_id", + "status", + "user_id", + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation_id_status_user_id_created_at` ON `${TABLE_NAME}` (`conversation_id`, `status`, `user_id`, `created_at`)" + } + ], + "foreignKeys": [ + { + "table": "conversations", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "conversation_id" + ], + "referencedColumns": [ + "conversation_id" + ] + } + ] + }, + { + "tableName": "participants", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `role` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`conversation_id`, `user_id`), FOREIGN KEY(`conversation_id`) REFERENCES `conversations`(`conversation_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id", + "user_id" + ] + }, + "foreignKeys": [ + { + "table": "conversations", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "conversation_id" + ], + "referencedColumns": [ + "conversation_id" + ] + } + ] + }, + { + "tableName": "participant_session", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `session_id` TEXT NOT NULL, `sent_to_server` INTEGER, `created_at` TEXT, `public_key` TEXT, PRIMARY KEY(`conversation_id`, `user_id`, `session_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sessionId", + "columnName": "session_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sentToServer", + "columnName": "sent_to_server", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT" + }, + { + "fieldPath": "publicKey", + "columnName": "public_key", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id", + "user_id", + "session_id" + ] + } + }, + { + "tableName": "offsets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `timestamp` TEXT NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + } + }, + { + "tableName": "assets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `symbol` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `balance` TEXT NOT NULL, `destination` TEXT NOT NULL, `tag` TEXT, `price_btc` TEXT NOT NULL, `price_usd` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `change_usd` TEXT NOT NULL, `change_btc` TEXT NOT NULL, `confirmations` INTEGER NOT NULL, `asset_key` TEXT, `reserve` TEXT, `deposit_entries` TEXT, `withdrawal_memo_possibility` TEXT, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "priceBtc", + "columnName": "price_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceUsd", + "columnName": "price_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeUsd", + "columnName": "change_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeBtc", + "columnName": "change_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "assetKey", + "columnName": "asset_key", + "affinity": "TEXT" + }, + { + "fieldPath": "reserve", + "columnName": "reserve", + "affinity": "TEXT" + }, + { + "fieldPath": "depositEntries", + "columnName": "deposit_entries", + "affinity": "TEXT" + }, + { + "fieldPath": "withdrawalMemoPossibility", + "columnName": "withdrawal_memo_possibility", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + } + }, + { + "tableName": "assets_extra", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `hidden` INTEGER, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + } + }, + { + "tableName": "tokens_extra", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `kernel_asset_id` TEXT NOT NULL, `hidden` INTEGER, `balance` TEXT, `updated_at` TEXT NOT NULL, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "asset", + "columnName": "kernel_asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "TEXT" + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + }, + "indices": [ + { + "name": "index_tokens_extra_kernel_asset_id", + "unique": false, + "columnNames": [ + "kernel_asset_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tokens_extra_kernel_asset_id` ON `${TABLE_NAME}` (`kernel_asset_id`)" + } + ] + }, + { + "tableName": "snapshots", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`snapshot_id` TEXT NOT NULL, `type` TEXT NOT NULL, `asset_id` TEXT NOT NULL, `amount` TEXT NOT NULL, `created_at` TEXT NOT NULL, `opponent_id` TEXT, `trace_id` TEXT, `transaction_hash` TEXT, `sender` TEXT, `receiver` TEXT, `memo` TEXT, `confirmations` INTEGER, `snapshot_hash` TEXT, `opening_balance` TEXT, `closing_balance` TEXT, PRIMARY KEY(`snapshot_id`))", + "fields": [ + { + "fieldPath": "snapshotId", + "columnName": "snapshot_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "opponentId", + "columnName": "opponent_id", + "affinity": "TEXT" + }, + { + "fieldPath": "traceId", + "columnName": "trace_id", + "affinity": "TEXT" + }, + { + "fieldPath": "transactionHash", + "columnName": "transaction_hash", + "affinity": "TEXT" + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT" + }, + { + "fieldPath": "receiver", + "columnName": "receiver", + "affinity": "TEXT" + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "TEXT" + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER" + }, + { + "fieldPath": "snapshotHash", + "columnName": "snapshot_hash", + "affinity": "TEXT" + }, + { + "fieldPath": "openingBalance", + "columnName": "opening_balance", + "affinity": "TEXT" + }, + { + "fieldPath": "closingBalance", + "columnName": "closing_balance", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "snapshot_id" + ] + } + }, + { + "tableName": "messages_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + } + }, + { + "tableName": "sent_sender_keys", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `sent_to_server` INTEGER NOT NULL, `sender_key_id` INTEGER, `created_at` TEXT, PRIMARY KEY(`conversation_id`, `user_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sentToServer", + "columnName": "sent_to_server", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderKeyId", + "columnName": "sender_key_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id", + "user_id" + ] + } + }, + { + "tableName": "stickers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sticker_id` TEXT NOT NULL, `album_id` TEXT, `name` TEXT NOT NULL, `asset_url` TEXT NOT NULL, `asset_type` TEXT NOT NULL, `asset_width` INTEGER NOT NULL, `asset_height` INTEGER NOT NULL, `created_at` TEXT NOT NULL, `last_use_at` TEXT, PRIMARY KEY(`sticker_id`))", + "fields": [ + { + "fieldPath": "stickerId", + "columnName": "sticker_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetUrl", + "columnName": "asset_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetType", + "columnName": "asset_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetWidth", + "columnName": "asset_width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "assetHeight", + "columnName": "asset_height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUseAt", + "columnName": "last_use_at", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "sticker_id" + ] + } + }, + { + "tableName": "sticker_albums", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`album_id` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `created_at` TEXT NOT NULL, `update_at` TEXT NOT NULL, `user_id` TEXT NOT NULL, `category` TEXT NOT NULL, `description` TEXT NOT NULL, `banner` TEXT, `is_verified` INTEGER NOT NULL, `ordered_at` INTEGER NOT NULL DEFAULT 0, `added` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`album_id`))", + "fields": [ + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updateAt", + "columnName": "update_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "banner", + "columnName": "banner", + "affinity": "TEXT" + }, + { + "fieldPath": "isVerified", + "columnName": "is_verified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "orderedAt", + "columnName": "ordered_at", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "added", + "columnName": "added", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "album_id" + ] + } + }, + { + "tableName": "apps", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`app_id` TEXT NOT NULL, `app_number` TEXT NOT NULL, `home_uri` TEXT NOT NULL, `redirect_uri` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `category` TEXT, `description` TEXT NOT NULL, `app_secret` TEXT NOT NULL, `capabilities` TEXT, `creator_id` TEXT NOT NULL, `resource_patterns` TEXT, `updated_at` TEXT, PRIMARY KEY(`app_id`))", + "fields": [ + { + "fieldPath": "appId", + "columnName": "app_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appNumber", + "columnName": "app_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "homeUri", + "columnName": "home_uri", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "redirectUri", + "columnName": "redirect_uri", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT" + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appSecret", + "columnName": "app_secret", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "capabilities", + "columnName": "capabilities", + "affinity": "TEXT" + }, + { + "fieldPath": "creatorId", + "columnName": "creator_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resourcePatterns", + "columnName": "resource_patterns", + "affinity": "TEXT" + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "app_id" + ] + } + }, + { + "tableName": "hyperlinks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`hyperlink` TEXT NOT NULL, `site_name` TEXT NOT NULL, `site_title` TEXT NOT NULL, `site_description` TEXT, `site_image` TEXT, PRIMARY KEY(`hyperlink`))", + "fields": [ + { + "fieldPath": "hyperlink", + "columnName": "hyperlink", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "siteName", + "columnName": "site_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "siteTitle", + "columnName": "site_title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "siteDescription", + "columnName": "site_description", + "affinity": "TEXT" + }, + { + "fieldPath": "siteImage", + "columnName": "site_image", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "hyperlink" + ] + } + }, + { + "tableName": "flood_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `data` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + } + }, + { + "tableName": "addresses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address_id` TEXT NOT NULL, `type` TEXT NOT NULL, `asset_id` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `destination` TEXT NOT NULL, `label` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `tag` TEXT, `dust` TEXT, PRIMARY KEY(`address_id`))", + "fields": [ + { + "fieldPath": "addressId", + "columnName": "address_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "dust", + "columnName": "dust", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "address_id" + ] + }, + "indices": [ + { + "name": "index_addresses_chain_id_updated_at", + "unique": false, + "columnNames": [ + "chain_id", + "updated_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_addresses_chain_id_updated_at` ON `${TABLE_NAME}` (`chain_id`, `updated_at`)" + } + ] + }, + { + "tableName": "resend_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `status` INTEGER NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`message_id`, `user_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id", + "user_id" + ] + } + }, + { + "tableName": "resend_session_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `session_id` TEXT NOT NULL, `status` INTEGER NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`message_id`, `user_id`, `session_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sessionId", + "columnName": "session_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id", + "user_id", + "session_id" + ] + } + }, + { + "tableName": "sticker_relationships", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`album_id` TEXT NOT NULL, `sticker_id` TEXT NOT NULL, PRIMARY KEY(`album_id`, `sticker_id`))", + "fields": [ + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stickerId", + "columnName": "sticker_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "album_id", + "sticker_id" + ] + } + }, + { + "tableName": "top_assets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `symbol` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `balance` TEXT NOT NULL, `destination` TEXT NOT NULL, `tag` TEXT, `price_btc` TEXT NOT NULL, `price_usd` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `change_usd` TEXT NOT NULL, `change_btc` TEXT NOT NULL, `confirmations` INTEGER NOT NULL, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "priceBtc", + "columnName": "price_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceUsd", + "columnName": "price_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeUsd", + "columnName": "change_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeBtc", + "columnName": "change_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + } + }, + { + "tableName": "favorite_apps", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`app_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`app_id`, `user_id`))", + "fields": [ + { + "fieldPath": "appId", + "columnName": "app_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "app_id", + "user_id" + ] + } + }, + { + "tableName": "jobs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`job_id` TEXT NOT NULL, `action` TEXT NOT NULL, `created_at` TEXT NOT NULL, `order_id` INTEGER, `priority` INTEGER NOT NULL, `user_id` TEXT, `blaze_message` TEXT, `conversation_id` TEXT, `resend_message_id` TEXT, `run_count` INTEGER NOT NULL, PRIMARY KEY(`job_id`))", + "fields": [ + { + "fieldPath": "jobId", + "columnName": "job_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "action", + "columnName": "action", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "orderId", + "columnName": "order_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "blazeMessage", + "columnName": "blaze_message", + "affinity": "TEXT" + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT" + }, + { + "fieldPath": "resendMessageId", + "columnName": "resend_message_id", + "affinity": "TEXT" + }, + { + "fieldPath": "runCount", + "columnName": "run_count", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "job_id" + ] + }, + "indices": [ + { + "name": "index_jobs_action", + "unique": false, + "columnNames": [ + "action" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_jobs_action` ON `${TABLE_NAME}` (`action`)" + } + ] + }, + { + "tableName": "message_mentions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `mentions` TEXT NOT NULL, `has_read` INTEGER NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasRead", + "columnName": "has_read", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + }, + "indices": [ + { + "name": "index_message_mentions_conversation_id", + "unique": false, + "columnNames": [ + "conversation_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_mentions_conversation_id` ON `${TABLE_NAME}` (`conversation_id`)" + } + ] + }, + { + "tableName": "messages_fts4", + "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`message_id` TEXT NOT NULL, `content` TEXT, tokenize=unicode61, notindexed=`message_id`)", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [] + }, + "ftsVersion": "FTS4", + "ftsOptions": { + "tokenizer": "unicode61", + "tokenizerArgs": [], + "contentTable": "", + "languageIdColumnName": "", + "matchInfo": "FTS4", + "notIndexedColumns": [ + "message_id" + ], + "prefixSizes": [], + "preferredOrder": "ASC" + }, + "contentSyncTriggers": [] + }, + { + "tableName": "circles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`circle_id` TEXT NOT NULL, `name` TEXT NOT NULL, `created_at` TEXT NOT NULL, `ordered_at` TEXT, PRIMARY KEY(`circle_id`))", + "fields": [ + { + "fieldPath": "circleId", + "columnName": "circle_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "orderedAt", + "columnName": "ordered_at", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "circle_id" + ] + } + }, + { + "tableName": "circle_conversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `circle_id` TEXT NOT NULL, `user_id` TEXT, `created_at` TEXT NOT NULL, `pin_time` TEXT, PRIMARY KEY(`conversation_id`, `circle_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "circleId", + "columnName": "circle_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pinTime", + "columnName": "pin_time", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id", + "circle_id" + ] + } + }, + { + "tableName": "traces", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`trace_id` TEXT NOT NULL, `asset_id` TEXT NOT NULL, `amount` TEXT NOT NULL, `opponent_id` TEXT, `destination` TEXT, `tag` TEXT, `snapshot_id` TEXT, `created_at` TEXT NOT NULL, PRIMARY KEY(`trace_id`))", + "fields": [ + { + "fieldPath": "traceId", + "columnName": "trace_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "opponentId", + "columnName": "opponent_id", + "affinity": "TEXT" + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT" + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "snapshotId", + "columnName": "snapshot_id", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "trace_id" + ] + } + }, + { + "tableName": "transcript_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`transcript_id` TEXT NOT NULL, `message_id` TEXT NOT NULL, `user_id` TEXT, `user_full_name` TEXT, `category` TEXT NOT NULL, `created_at` TEXT NOT NULL, `content` TEXT, `media_url` TEXT, `media_name` TEXT, `media_size` INTEGER, `media_width` INTEGER, `media_height` INTEGER, `media_mime_type` TEXT, `media_duration` INTEGER, `media_status` TEXT, `media_waveform` BLOB, `thumb_image` TEXT, `thumb_url` TEXT, `media_key` BLOB, `media_digest` BLOB, `media_created_at` TEXT, `sticker_id` TEXT, `shared_user_id` TEXT, `mentions` TEXT, `quote_id` TEXT, `quote_content` TEXT, `caption` TEXT, PRIMARY KEY(`transcript_id`, `message_id`))", + "fields": [ + { + "fieldPath": "transcriptId", + "columnName": "transcript_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "userFullName", + "columnName": "user_full_name", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaUrl", + "columnName": "media_url", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaName", + "columnName": "media_name", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaSize", + "columnName": "media_size", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaWidth", + "columnName": "media_width", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaHeight", + "columnName": "media_height", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaMimeType", + "columnName": "media_mime_type", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaDuration", + "columnName": "media_duration", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaStatus", + "columnName": "media_status", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaWaveform", + "columnName": "media_waveform", + "affinity": "BLOB" + }, + { + "fieldPath": "thumbImage", + "columnName": "thumb_image", + "affinity": "TEXT" + }, + { + "fieldPath": "thumbUrl", + "columnName": "thumb_url", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaKey", + "columnName": "media_key", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaDigest", + "columnName": "media_digest", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaCreatedAt", + "columnName": "media_created_at", + "affinity": "TEXT" + }, + { + "fieldPath": "stickerId", + "columnName": "sticker_id", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedUserId", + "columnName": "shared_user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT" + }, + { + "fieldPath": "quoteId", + "columnName": "quote_id", + "affinity": "TEXT" + }, + { + "fieldPath": "quoteContent", + "columnName": "quote_content", + "affinity": "TEXT" + }, + { + "fieldPath": "caption", + "columnName": "caption", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "transcript_id", + "message_id" + ] + } + }, + { + "tableName": "pin_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + }, + "indices": [ + { + "name": "index_pin_messages_conversation_id_created_at", + "unique": false, + "columnNames": [ + "conversation_id", + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pin_messages_conversation_id_created_at` ON `${TABLE_NAME}` (`conversation_id`, `created_at`)" + } + ] + }, + { + "tableName": "properties", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT NOT NULL, `updated_at` TEXT NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + } + }, + { + "tableName": "remote_messages_status", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `status` TEXT NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + }, + "indices": [ + { + "name": "index_remote_messages_status_conversation_id_status", + "unique": false, + "columnNames": [ + "conversation_id", + "status" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_remote_messages_status_conversation_id_status` ON `${TABLE_NAME}` (`conversation_id`, `status`)" + } + ] + }, + { + "tableName": "expired_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `expire_in` INTEGER NOT NULL, `expire_at` INTEGER, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "expireIn", + "columnName": "expire_in", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expireAt", + "columnName": "expire_at", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + } + }, + { + "tableName": "conversation_ext", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `count` INTEGER NOT NULL DEFAULT 0, `created_at` TEXT NOT NULL, PRIMARY KEY(`conversation_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "count", + "columnName": "count", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id" + ] + } + }, + { + "tableName": "chains", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`chain_id` TEXT NOT NULL, `name` TEXT NOT NULL, `symbol` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `threshold` INTEGER NOT NULL, `withdrawal_memo_possibility` TEXT NOT NULL, PRIMARY KEY(`chain_id`))", + "fields": [ + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "threshold", + "columnName": "threshold", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "withdrawalMemoPossibility", + "columnName": "withdrawal_memo_possibility", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "chain_id" + ] + } + }, + { + "tableName": "outputs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`output_id` TEXT NOT NULL, `transaction_hash` TEXT NOT NULL, `output_index` INTEGER NOT NULL, `asset` TEXT NOT NULL, `sequence` INTEGER NOT NULL, `amount` TEXT NOT NULL, `mask` TEXT NOT NULL, `keys` TEXT NOT NULL, `receivers` TEXT NOT NULL, `receivers_hash` TEXT NOT NULL, `receivers_threshold` INTEGER NOT NULL, `extra` TEXT NOT NULL, `state` TEXT NOT NULL, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `signed_by` TEXT NOT NULL, `signed_at` TEXT NOT NULL, `spent_at` TEXT NOT NULL, `inscription_hash` TEXT, PRIMARY KEY(`output_id`))", + "fields": [ + { + "fieldPath": "outputId", + "columnName": "output_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionHash", + "columnName": "transaction_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "outputIndex", + "columnName": "output_index", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "asset", + "columnName": "asset", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sequence", + "columnName": "sequence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mask", + "columnName": "mask", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "keys", + "columnName": "keys", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "receivers", + "columnName": "receivers", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "receiversHash", + "columnName": "receivers_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "receiversThreshold", + "columnName": "receivers_threshold", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "extra", + "columnName": "extra", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "signedBy", + "columnName": "signed_by", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "signedAt", + "columnName": "signed_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "spentAt", + "columnName": "spent_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "inscriptionHash", + "columnName": "inscription_hash", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "output_id" + ] + }, + "indices": [ + { + "name": "index_outputs_asset_state_sequence", + "unique": false, + "columnNames": [ + "asset", + "state", + "sequence" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_outputs_asset_state_sequence` ON `${TABLE_NAME}` (`asset`, `state`, `sequence`)" + }, + { + "name": "index_outputs_transaction_hash_output_index", + "unique": true, + "columnNames": [ + "transaction_hash", + "output_index" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_outputs_transaction_hash_output_index` ON `${TABLE_NAME}` (`transaction_hash`, `output_index`)" + }, + { + "name": "index_outputs_inscription_hash", + "unique": false, + "columnNames": [ + "inscription_hash" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_outputs_inscription_hash` ON `${TABLE_NAME}` (`inscription_hash`)" + } + ] + }, + { + "tableName": "tokens", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `kernel_asset_id` TEXT NOT NULL, `symbol` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `price_btc` TEXT NOT NULL, `price_usd` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `change_usd` TEXT NOT NULL, `change_btc` TEXT NOT NULL, `confirmations` INTEGER NOT NULL, `asset_key` TEXT NOT NULL, `dust` TEXT NOT NULL, `collection_hash` TEXT, `precision` INTEGER NOT NULL, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "asset", + "columnName": "kernel_asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceBtc", + "columnName": "price_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceUsd", + "columnName": "price_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeUsd", + "columnName": "change_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeBtc", + "columnName": "change_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "assetKey", + "columnName": "asset_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dust", + "columnName": "dust", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "collectionHash", + "columnName": "collection_hash", + "affinity": "TEXT" + }, + { + "fieldPath": "precision", + "columnName": "precision", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + }, + "indices": [ + { + "name": "index_tokens_kernel_asset_id", + "unique": false, + "columnNames": [ + "kernel_asset_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tokens_kernel_asset_id` ON `${TABLE_NAME}` (`kernel_asset_id`)" + }, + { + "name": "index_tokens_collection_hash", + "unique": false, + "columnNames": [ + "collection_hash" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tokens_collection_hash` ON `${TABLE_NAME}` (`collection_hash`)" + } + ] + }, + { + "tableName": "deposit_entries", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entry_id` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `destination` TEXT NOT NULL, `members` TEXT NOT NULL, `tag` TEXT, `signature` TEXT NOT NULL, `threshold` INTEGER NOT NULL, `is_primary` INTEGER NOT NULL, `minimum` TEXT NOT NULL, `maximum` TEXT NOT NULL, PRIMARY KEY(`entry_id`))", + "fields": [ + { + "fieldPath": "entryId", + "columnName": "entry_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "members", + "columnName": "members", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "signature", + "columnName": "signature", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "threshold", + "columnName": "threshold", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPrimary", + "columnName": "is_primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minimum", + "columnName": "minimum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "maximum", + "columnName": "maximum", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "entry_id" + ] + } + }, + { + "tableName": "safe_snapshots", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`snapshot_id` TEXT NOT NULL, `type` TEXT NOT NULL, `asset_id` TEXT NOT NULL, `amount` TEXT NOT NULL, `user_id` TEXT NOT NULL, `opponent_id` TEXT NOT NULL, `memo` TEXT NOT NULL, `transaction_hash` TEXT NOT NULL, `created_at` TEXT NOT NULL, `trace_id` TEXT, `confirmations` INTEGER, `opening_balance` TEXT, `closing_balance` TEXT, `deposit` TEXT, `withdrawal` TEXT, `inscription_hash` TEXT, PRIMARY KEY(`snapshot_id`))", + "fields": [ + { + "fieldPath": "snapshotId", + "columnName": "snapshot_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "opponentId", + "columnName": "opponent_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionHash", + "columnName": "transaction_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "traceId", + "columnName": "trace_id", + "affinity": "TEXT" + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER" + }, + { + "fieldPath": "openingBalance", + "columnName": "opening_balance", + "affinity": "TEXT" + }, + { + "fieldPath": "closingBalance", + "columnName": "closing_balance", + "affinity": "TEXT" + }, + { + "fieldPath": "deposit", + "columnName": "deposit", + "affinity": "TEXT" + }, + { + "fieldPath": "withdrawal", + "columnName": "withdrawal", + "affinity": "TEXT" + }, + { + "fieldPath": "inscriptionHash", + "columnName": "inscription_hash", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "snapshot_id" + ] + }, + "indices": [ + { + "name": "index_safe_snapshots_created_at", + "unique": false, + "columnNames": [ + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_safe_snapshots_created_at` ON `${TABLE_NAME}` (`created_at`)" + }, + { + "name": "index_safe_snapshots_type_asset_id", + "unique": false, + "columnNames": [ + "type", + "asset_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_safe_snapshots_type_asset_id` ON `${TABLE_NAME}` (`type`, `asset_id`)" + } + ] + }, + { + "tableName": "raw_transactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`request_id` TEXT NOT NULL, `raw_transaction` TEXT NOT NULL, `receiver_id` TEXT NOT NULL, `type` INTEGER NOT NULL, `state` TEXT NOT NULL, `created_at` TEXT NOT NULL, `inscription_hash` TEXT, PRIMARY KEY(`request_id`))", + "fields": [ + { + "fieldPath": "requestId", + "columnName": "request_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rawTransaction", + "columnName": "raw_transaction", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "receiverId", + "columnName": "receiver_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "inscriptionHash", + "columnName": "inscription_hash", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "request_id" + ] + }, + "indices": [ + { + "name": "index_raw_transactions_state_type", + "unique": false, + "columnNames": [ + "state", + "type" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_raw_transactions_state_type` ON `${TABLE_NAME}` (`state`, `type`)" + } + ] + }, + { + "tableName": "inscription_collections", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`collection_hash` TEXT NOT NULL, `supply` TEXT NOT NULL, `unit` TEXT NOT NULL, `symbol` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `description` TEXT, `kernel_asset_id` TEXT, `treasury` TEXT, PRIMARY KEY(`collection_hash`))", + "fields": [ + { + "fieldPath": "collectionHash", + "columnName": "collection_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "supply", + "columnName": "supply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unit", + "columnName": "unit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconURL", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT" + }, + { + "fieldPath": "kernelAssetId", + "columnName": "kernel_asset_id", + "affinity": "TEXT" + }, + { + "fieldPath": "treasury", + "columnName": "treasury", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "collection_hash" + ] + } + }, + { + "tableName": "inscription_items", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`inscription_hash` TEXT NOT NULL, `collection_hash` TEXT NOT NULL, `sequence` INTEGER NOT NULL, `content_type` TEXT NOT NULL, `content_url` TEXT NOT NULL, `occupied_by` TEXT, `occupied_at` TEXT, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `owner` TEXT, `traits` TEXT, PRIMARY KEY(`inscription_hash`))", + "fields": [ + { + "fieldPath": "inscriptionHash", + "columnName": "inscription_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "collectionHash", + "columnName": "collection_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sequence", + "columnName": "sequence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contentType", + "columnName": "content_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contentURL", + "columnName": "content_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "occupiedBy", + "columnName": "occupied_by", + "affinity": "TEXT" + }, + { + "fieldPath": "occupiedAt", + "columnName": "occupied_at", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "owner", + "columnName": "owner", + "affinity": "TEXT" + }, + { + "fieldPath": "traits", + "columnName": "traits", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "inscription_hash" + ] + } + }, + { + "tableName": "markets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`coin_id` TEXT NOT NULL, `name` TEXT NOT NULL, `symbol` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `current_price` TEXT NOT NULL, `market_cap` TEXT NOT NULL, `market_cap_rank` TEXT NOT NULL, `total_volume` TEXT NOT NULL, `high_24h` TEXT NOT NULL, `low_24h` TEXT NOT NULL, `price_change_24h` TEXT NOT NULL, `price_change_percentage_1h` TEXT NOT NULL, `price_change_percentage_24h` TEXT NOT NULL, `price_change_percentage_7d` TEXT NOT NULL, `price_change_percentage_30d` TEXT NOT NULL, `market_cap_change_24h` TEXT NOT NULL, `market_cap_change_percentage_24h` TEXT NOT NULL, `circulating_supply` TEXT NOT NULL, `total_supply` TEXT NOT NULL, `max_supply` TEXT NOT NULL, `ath` TEXT NOT NULL, `ath_change_percentage` TEXT NOT NULL, `ath_date` TEXT NOT NULL, `atl` TEXT NOT NULL, `atl_change_percentage` TEXT NOT NULL, `atl_date` TEXT NOT NULL, `asset_ids` TEXT, `sparkline_in_7d` TEXT NOT NULL, `sparkline_in_24h` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `descriptions` TEXT, PRIMARY KEY(`coin_id`))", + "fields": [ + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentPrice", + "columnName": "current_price", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCap", + "columnName": "market_cap", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCapRank", + "columnName": "market_cap_rank", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "totalVolume", + "columnName": "total_volume", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "high24h", + "columnName": "high_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "low24h", + "columnName": "low_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChange24h", + "columnName": "price_change_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChangePercentage1H", + "columnName": "price_change_percentage_1h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChangePercentage24H", + "columnName": "price_change_percentage_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChangePercentage7D", + "columnName": "price_change_percentage_7d", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChangePercentage30D", + "columnName": "price_change_percentage_30d", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCapChange24h", + "columnName": "market_cap_change_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCapChangePercentage24h", + "columnName": "market_cap_change_percentage_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "circulatingSupply", + "columnName": "circulating_supply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "totalSupply", + "columnName": "total_supply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "maxSupply", + "columnName": "max_supply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ath", + "columnName": "ath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "athChangePercentage", + "columnName": "ath_change_percentage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "athDate", + "columnName": "ath_date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "atl", + "columnName": "atl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "atlChangePercentage", + "columnName": "atl_change_percentage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "atlDate", + "columnName": "atl_date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetIds", + "columnName": "asset_ids", + "affinity": "TEXT" + }, + { + "fieldPath": "sparklineIn7d", + "columnName": "sparkline_in_7d", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sparklineIn24h", + "columnName": "sparkline_in_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "descriptions", + "columnName": "descriptions", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "coin_id" + ] + } + }, + { + "tableName": "history_prices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`coin_id` TEXT NOT NULL, `type` TEXT NOT NULL, `data` TEXT NOT NULL, `updated_at` TEXT NOT NULL, PRIMARY KEY(`coin_id`, `type`))", + "fields": [ + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "coin_id", + "type" + ] + } + }, + { + "tableName": "market_coins", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `coin_id` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + } + }, + { + "tableName": "market_favored", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`coin_id` TEXT NOT NULL, `is_favored` INTEGER NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`coin_id`))", + "fields": [ + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isFavored", + "columnName": "is_favored", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "coin_id" + ] + } + }, + { + "tableName": "market_alerts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`alert_id` TEXT NOT NULL, `coin_id` TEXT NOT NULL, `type` TEXT NOT NULL, `frequency` TEXT NOT NULL, `status` TEXT NOT NULL, `value` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`alert_id`))", + "fields": [ + { + "fieldPath": "alertId", + "columnName": "alert_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "frequency", + "columnName": "frequency", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "alert_id" + ] + } + }, + { + "tableName": "market_cap_ranks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`coin_id` TEXT NOT NULL, `market_cap_rank` TEXT NOT NULL, `updated_at` TEXT NOT NULL, PRIMARY KEY(`coin_id`))", + "fields": [ + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCapRank", + "columnName": "market_cap_rank", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "coin_id" + ] + } + }, + { + "tableName": "membership_orders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`order_id` TEXT NOT NULL, `category` TEXT NOT NULL, `amount` TEXT NOT NULL, `amount_actual` TEXT NOT NULL, `amount_original` TEXT NOT NULL, `after` TEXT NOT NULL, `before` TEXT NOT NULL, `created_at` TEXT NOT NULL, `fiat_order` TEXT, `stars` INTEGER NOT NULL, `payment_url` TEXT, `status` TEXT NOT NULL, PRIMARY KEY(`order_id`))", + "fields": [ + { + "fieldPath": "orderId", + "columnName": "order_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amountActual", + "columnName": "amount_actual", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amountOriginal", + "columnName": "amount_original", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "after", + "columnName": "after", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "before", + "columnName": "before", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fiatOrder", + "columnName": "fiat_order", + "affinity": "TEXT" + }, + { + "fieldPath": "stars", + "columnName": "stars", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "paymentUrl", + "columnName": "payment_url", + "affinity": "TEXT" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "order_id" + ] + }, + "indices": [ + { + "name": "index_membership_orders_created_at", + "unique": false, + "columnNames": [ + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_membership_orders_created_at` ON `${TABLE_NAME}` (`created_at`)" + } + ] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '61cb9345a060036bd9618e3986544805')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/one/mixin/android/Constants.kt b/app/src/main/java/one/mixin/android/Constants.kt index d84b0e37de..be575f4fbd 100644 --- a/app/src/main/java/one/mixin/android/Constants.kt +++ b/app/src/main/java/one/mixin/android/Constants.kt @@ -216,7 +216,7 @@ object Constants { object DataBase { const val DB_NAME = "mixin.db" const val MINI_VERSION = 15 - const val CURRENT_VERSION = 70 + const val CURRENT_VERSION = 71 const val FTS_DB_NAME = "fts.db" const val PENDING_DB_NAME = "pending.db" diff --git a/app/src/main/java/one/mixin/android/db/MessageDao.kt b/app/src/main/java/one/mixin/android/db/MessageDao.kt index 96aaf2d3ac..88705e4010 100644 --- a/app/src/main/java/one/mixin/android/db/MessageDao.kt +++ b/app/src/main/java/one/mixin/android/db/MessageDao.kt @@ -38,13 +38,13 @@ interface MessageDao : BaseDao { m.name AS mediaName, m.media_mime_type AS mediaMimeType, m.media_size AS mediaSize, m.media_width AS mediaWidth, m.media_height AS mediaHeight, m.thumb_image AS thumbImage, m.thumb_url AS thumbUrl, m.media_url AS mediaUrl, m.media_duration AS mediaDuration, m.quote_message_id as quoteId, m.quote_content as quoteContent, m.caption as caption, u1.full_name AS participantFullName, m.action AS actionName, u1.user_id AS participantUserId, - COALESCE(s.snapshot_id, ss.snapshot_id) AS snapshotId, COALESCE(s.type, ss.type) AS snapshotType, COALESCE(s.memo, ss.memo) AS snapshotMemo, COALESCE(s.amount, ss.amount) AS snapshotAmount, + COALESCE(s.snapshot_id, ss.snapshot_id) AS snapshotId, COALESCE(s.type, ss.type) AS snapshotType, COALESCE(s.memo, ss.memo) AS snapshotMemo, COALESCE(s.amount, ss.amount) AS snapshotAmount, COALESCE(a.symbol, t.symbol) AS assetSymbol, COALESCE(s.asset_id, ss.asset_id) AS assetId, COALESCE(a.icon_url, t.icon_url) AS assetIcon, st.asset_url AS assetUrl, st.asset_width AS assetWidth, st.asset_height AS assetHeight, st.sticker_id AS stickerId, st.name AS assetName, st.asset_type AS assetType, h.site_name AS siteName, h.site_title AS siteTitle, h.site_description AS siteDescription, h.site_image AS siteImage, m.shared_user_id AS sharedUserId, su.full_name AS sharedUserFullName, su.identity_number AS sharedUserIdentityNumber, - su.avatar_url AS sharedUserAvatarUrl, su.is_verified AS sharedUserIsVerified, su.app_id AS sharedUserAppId, mm.mentions AS mentions, mm.has_read as mentionRead, - pm.message_id IS NOT NULL as isPin, c.name AS groupName, em.expire_in AS expireIn, em.expire_at AS expireAt + su.avatar_url AS sharedUserAvatarUrl, su.is_verified AS sharedUserIsVerified, su.app_id AS sharedUserAppId, mm.mentions AS mentions, mm.has_read as mentionRead, + pm.message_id IS NOT NULL as isPin, c.name AS groupName, em.expire_in AS expireIn, em.expire_at AS expireAt FROM messages m INNER JOIN users u ON m.user_id = u.user_id LEFT JOIN users u1 ON m.participant_id = u1.user_id @@ -56,7 +56,7 @@ interface MessageDao : BaseDao { LEFT JOIN hyperlinks h ON m.hyperlink = h.hyperlink LEFT JOIN users su ON m.shared_user_id = su.user_id LEFT JOIN conversations c ON m.conversation_id = c.conversation_id - LEFT JOIN message_mentions mm ON m.id = mm.message_id + LEFT JOIN message_mentions mm ON m.id = mm.message_id LEFT JOIN pin_messages pm ON m.id = pm.message_id LEFT JOIN expired_messages em ON m.id = em.message_id """ @@ -96,7 +96,7 @@ interface MessageDao : BaseDao { m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.media_duration AS mediaDuration FROM messages m INDEXED BY index_messages_conversation_id_category - INNER JOIN users u ON m.user_id = u.user_id + INNER JOIN users u ON m.user_id = u.user_id WHERE m.conversation_id = :conversationId AND (m.category IN ($IMAGES, $VIDEOS, $LIVES) OR ($APP_CARD_COVER_MEDIA_ALIAS)) ORDER BY m.created_at ASC, m.rowid ASC @@ -114,7 +114,7 @@ interface MessageDao : BaseDao { m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.media_duration AS mediaDuration FROM messages m INDEXED BY index_messages_conversation_id_category - INNER JOIN users u ON m.user_id = u.user_id + INNER JOIN users u ON m.user_id = u.user_id WHERE m.conversation_id = :conversationId AND (m.category IN ($IMAGES, $VIDEOS, $LIVES) OR ($APP_CARD_COVER_MEDIA_ALIAS)) ORDER BY m.created_at ASC, m.rowid ASC @@ -132,7 +132,7 @@ interface MessageDao : BaseDao { m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.media_duration AS mediaDuration FROM messages m INDEXED BY index_messages_conversation_id_category - INNER JOIN users u ON m.user_id = u.user_id + INNER JOIN users u ON m.user_id = u.user_id WHERE m.conversation_id = :conversationId AND m.category IN ($IMAGES, $VIDEOS) ORDER BY m.created_at DESC, m.rowid DESC @@ -148,8 +148,8 @@ interface MessageDao : BaseDao { m.content AS content, m.created_at AS createdAt, m.status AS status, m.media_status AS mediaStatus, m.media_width AS mediaWidth, m.media_height AS mediaHeight, m.thumb_image AS thumbImage, m.thumb_url AS thumbUrl, m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.media_duration AS mediaDuration - FROM messages m - INNER JOIN users u ON m.user_id = u.user_id + FROM messages m + INNER JOIN users u ON m.user_id = u.user_id WHERE m.id = :messageId AND m.conversation_id = :conversationId AND (m.category IN ($IMAGES, $VIDEOS, $LIVES) OR ($APP_CARD_COVER_MEDIA_ALIAS)) """, @@ -161,7 +161,7 @@ interface MessageDao : BaseDao { @Query( """ - SELECT count(1) FROM messages + SELECT count(1) FROM messages INDEXED BY index_messages_conversation_id_category WHERE conversation_id = :conversationId AND (category IN ($IMAGES, $VIDEOS, $LIVES) OR ($APP_CARD_COVER_MEDIA)) @@ -202,7 +202,7 @@ interface MessageDao : BaseDao { @Query( """ SELECT count(1) FROM messages - WHERE conversation_id = :conversationId + WHERE conversation_id = :conversationId AND category IN ($IMAGES, $VIDEOS) AND (created_at > (SELECT created_at FROM messages WHERE id = :messageId) OR (created_at = (SELECT created_at FROM messages WHERE id = :messageId) AND rowid > (SELECT rowid FROM messages WHERE id = :messageId))) """, @@ -214,9 +214,9 @@ interface MessageDao : BaseDao { @Query( """ - SELECT count(1) FROM messages + SELECT count(1) FROM messages WHERE conversation_id = :conversationId - AND category IN ($IMAGES, $VIDEOS) + AND category IN ($IMAGES, $VIDEOS) """) suspend fun countIndexMediaMessagesExcludeLive(conversationId: String): Int @@ -228,7 +228,7 @@ interface MessageDao : BaseDao { m.content AS content, m.created_at AS createdAt, m.status AS status, m.media_status AS mediaStatus, m.media_width AS mediaWidth, m.media_height AS mediaHeight, m.thumb_image AS thumbImage, m.thumb_url AS thumbUrl, m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.media_duration AS mediaDuration, m.media_waveform AS mediaWaveform - FROM messages m INNER JOIN users u ON m.user_id = u.user_id + FROM messages m INNER JOIN users u ON m.user_id = u.user_id WHERE m.conversation_id = :conversationId AND m.category IN ($AUDIOS) ORDER BY m.created_at DESC @@ -244,7 +244,7 @@ interface MessageDao : BaseDao { m.content AS content, m.created_at AS createdAt, m.status AS status, m.media_status AS mediaStatus, m.media_width AS mediaWidth, m.media_height AS mediaHeight, m.thumb_image AS thumbImage, m.thumb_url AS thumbUrl, m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.media_duration AS mediaDuration, m.media_waveform AS mediaWaveform - FROM messages m INNER JOIN users u ON m.user_id = u.user_id + FROM messages m INNER JOIN users u ON m.user_id = u.user_id WHERE m.conversation_id = :conversationId AND m.category IN ('SIGNAL_POST', 'PLAIN_POST', 'ENCRYPTED_POST') ORDER BY m.created_at DESC @@ -260,8 +260,8 @@ interface MessageDao : BaseDao { m.content AS content, m.created_at AS createdAt, m.status AS status, m.media_status AS mediaStatus, m.media_size AS mediaSize, m.media_width AS mediaWidth, m.media_height AS mediaHeight, m.thumb_image AS thumbImage, m.thumb_url AS thumbUrl, m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.media_duration AS mediaDuration - FROM messages m - INNER JOIN users u ON m.user_id = u.user_id + FROM messages m + INNER JOIN users u ON m.user_id = u.user_id WHERE m.conversation_id = :conversationId AND m.category IN ($AUDIOS) ORDER BY m.created_at DESC @@ -288,7 +288,7 @@ interface MessageDao : BaseDao { u.full_name AS userFullName, u.identity_number AS userIdentityNumber, m.category AS type, m.content AS content, m.created_at AS createdAt, m.status AS status, m.media_status AS mediaStatus, m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.name AS mediaName, m.media_size AS mediaSize - FROM messages m INNER JOIN users u ON m.user_id = u.user_id + FROM messages m INNER JOIN users u ON m.user_id = u.user_id WHERE m.conversation_id = :conversationId AND m.category IN ($DATA) ORDER BY m.created_at DESC @@ -299,20 +299,20 @@ interface MessageDao : BaseDao { @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @Query( """ - SELECT m.id AS messageId, m.conversation_id AS conversationId, u.user_id AS userId, - u.full_name AS userFullName, u.identity_number AS userIdentityNumber, u.app_id AS appId, m.category AS type, - m.content AS content, m.created_at AS createdAt, m.status AS status, m.media_status AS mediaStatus, m.media_waveform AS mediaWaveform, - m.name AS mediaName, m.media_mime_type AS mediaMimeType, m.media_size AS mediaSize, m.media_width AS mediaWidth, m.media_height AS mediaHeight, - m.thumb_image AS thumbImage, m.thumb_url AS thumbUrl, m.media_url AS mediaUrl, m.media_duration AS mediaDuration, - m.quote_message_id as quoteId, m.quote_content as quoteContent, - st.asset_url AS assetUrl, st.asset_width AS assetWidth, st.asset_height AS assetHeight, st.sticker_id AS stickerId, - st.name AS assetName, st.asset_type AS assetType, m.shared_user_id AS sharedUserId, su.full_name AS sharedUserFullName, su.identity_number AS sharedUserIdentityNumber, - su.avatar_url AS sharedUserAvatarUrl, su.is_verified AS sharedUserIsVerified, su.app_id AS sharedUserAppId, mm.mentions AS mentions, u.membership - FROM messages m - INNER JOIN users u ON m.user_id = u.user_id - LEFT JOIN stickers st ON st.sticker_id = m.sticker_id - LEFT JOIN users su ON m.shared_user_id = su.user_id - LEFT JOIN message_mentions mm ON m.id = mm.message_id + SELECT m.id AS messageId, m.conversation_id AS conversationId, u.user_id AS userId, + u.full_name AS userFullName, u.identity_number AS userIdentityNumber, u.app_id AS appId, m.category AS type, + m.content AS content, m.created_at AS createdAt, m.status AS status, m.media_status AS mediaStatus, m.media_waveform AS mediaWaveform, + m.name AS mediaName, m.media_mime_type AS mediaMimeType, m.media_size AS mediaSize, m.media_width AS mediaWidth, m.media_height AS mediaHeight, + m.thumb_image AS thumbImage, m.thumb_url AS thumbUrl, m.media_url AS mediaUrl, m.media_duration AS mediaDuration, + m.quote_message_id as quoteId, m.quote_content as quoteContent, + st.asset_url AS assetUrl, st.asset_width AS assetWidth, st.asset_height AS assetHeight, st.sticker_id AS stickerId, + st.name AS assetName, st.asset_type AS assetType, m.shared_user_id AS sharedUserId, su.full_name AS sharedUserFullName, su.identity_number AS sharedUserIdentityNumber, + su.avatar_url AS sharedUserAvatarUrl, su.is_verified AS sharedUserIsVerified, su.app_id AS sharedUserAppId, mm.mentions AS mentions, u.membership + FROM messages m + INNER JOIN users u ON m.user_id = u.user_id + LEFT JOIN stickers st ON st.sticker_id = m.sticker_id + LEFT JOIN users su ON m.shared_user_id = su.user_id + LEFT JOIN message_mentions mm ON m.id = mm.message_id WHERE m.conversation_id = :conversationId AND m.id = :messageId AND m.status != 'FAILED' """, ) @@ -341,8 +341,8 @@ interface MessageDao : BaseDao { SELECT m.id AS messageId, u.user_id AS userId, u.avatar_url AS userAvatarUrl, u.full_name AS userFullName, m.category AS type, m.content AS content, m.created_at AS createdAt, m.name AS mediaName, u.membership AS membership, u.identity_number AS userIdentityNumber, - u.app_id AS app_id, u.is_verified AS isVerified - FROM messages m INNER JOIN users u ON m.user_id = u.user_id + u.app_id AS app_id, u.is_verified AS isVerified + FROM messages m INNER JOIN users u ON m.user_id = u.user_id WHERE m.id IN (:ids) ORDER BY m.created_at DESC """, @@ -387,8 +387,8 @@ interface MessageDao : BaseDao { @Query( """ - SELECT m.* FROM messages m - WHERE m.rowid < :rowId AND m.category IN ('SIGNAL_TEXT', 'PLAIN_TEXT', 'ENCRYPTED_TEXT', 'SIGNAL_TRANSCRIPT', 'PLAIN_TRANSCRIPT', 'ENCRYPTED_TRANSCRIPT', + SELECT m.* FROM messages m + WHERE m.rowid < :rowId AND m.category IN ('SIGNAL_TEXT', 'PLAIN_TEXT', 'ENCRYPTED_TEXT', 'SIGNAL_TRANSCRIPT', 'PLAIN_TRANSCRIPT', 'ENCRYPTED_TRANSCRIPT', 'SIGNAL_POST', 'PLAIN_POST', 'ENCRYPTED_POST', 'SIGNAL_DATA', 'PLAIN_DATA', 'ENCRYPTED_DATA', 'SIGNAL_CONTACT', 'PLAIN_CONTACT', 'ENCRYPTED_CONTACT', 'APP_CARD') AND m.status != 'FAILED' AND m.status != 'UNKNOWN' ORDER BY m.rowid DESC @@ -403,10 +403,10 @@ interface MessageDao : BaseDao { @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @Query( """ - SELECT m.* FROM messages m + SELECT m.* FROM messages m WHERE m.rowid >= :rowId ORDER BY m.rowid ASC - LIMIT :limit + LIMIT :limit """, ) fun getMessageByLimitAndRowId( @@ -417,10 +417,10 @@ interface MessageDao : BaseDao { @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @Query( """ - SELECT m.* FROM messages m - WHERE m.rowid >= :rowId AND m.conversation_id IN (:conversationIds) + SELECT m.* FROM messages m + WHERE m.rowid >= :rowId AND m.conversation_id IN (:conversationIds) ORDER BY m.rowid ASC - LIMIT :limit + LIMIT :limit """, ) fun getMessageByLimitAndRowId( @@ -432,10 +432,10 @@ interface MessageDao : BaseDao { @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @Query( """ - SELECT m.* FROM messages m - WHERE m.rowid >= :rowId AND m.created_at >= :createdAt + SELECT m.* FROM messages m + WHERE m.rowid >= :rowId AND m.created_at >= :createdAt ORDER BY m.rowid ASC - LIMIT :limit + LIMIT :limit """, ) fun getMessageByLimitAndRowId( @@ -447,10 +447,10 @@ interface MessageDao : BaseDao { @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @Query( """ - SELECT m.* FROM messages m - WHERE m.rowid >= :rowId AND m.conversation_id IN (:conversationIds) AND m.created_at >= :createdAt + SELECT m.* FROM messages m + WHERE m.rowid >= :rowId AND m.conversation_id IN (:conversationIds) AND m.created_at >= :createdAt ORDER BY m.rowid ASC - LIMIT :limit + LIMIT :limit """, ) fun getMessageByLimitAndRowId( @@ -490,8 +490,8 @@ interface MessageDao : BaseDao { @Query( """ - SELECT m.category as type, m.id as messageId, m.media_url as mediaUrl FROM messages m - WHERE conversation_id = :conversationId AND media_status = 'DONE' + SELECT m.category as type, m.id as messageId, m.media_url as mediaUrl FROM messages m + WHERE conversation_id = :conversationId AND media_status = 'DONE' AND category IN (:signalCategory, :plainCategory, :encryptedCategory) ORDER BY created_at ASC """, ) @@ -505,7 +505,7 @@ interface MessageDao : BaseDao { @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @Query( """ - $PREFIX_MESSAGE_ITEM WHERE m.conversation_id = :conversationId AND (m.category IN ($AUDIOS)) AND m.created_at >= :createdAt AND + $PREFIX_MESSAGE_ITEM WHERE m.conversation_id = :conversationId AND (m.category IN ($AUDIOS)) AND m.created_at >= :createdAt AND m.rowid > (SELECT rowid FROM messages WHERE id = :messageId) LIMIT 1 """, ) @@ -545,7 +545,7 @@ interface MessageDao : BaseDao { @Query( """ - SELECT id FROM messages WHERE conversation_id =:conversationId AND user_id !=:userId AND messages.rowid > + SELECT id FROM messages WHERE conversation_id =:conversationId AND user_id !=:userId AND messages.rowid > (SELECT rowid FROM messages WHERE id = :messageId) ORDER BY rowid ASC LIMIT 1 """, ) @@ -557,7 +557,7 @@ interface MessageDao : BaseDao { @Query( """ - SELECT rowid FROM messages + SELECT rowid FROM messages WHERE conversation_id =:conversationId AND status IN ('SENDING', 'SENT', 'DELIVERED', 'READ') /* Make use of `index_messages_conversation_id_status_user_id_created_at` */ AND user_id =:userId @@ -597,8 +597,8 @@ interface MessageDao : BaseDao { @Query( """ - SELECT id, conversation_id, name, category, media_url, media_mine_type - FROM messages WHERE category IN ($IMAGES, $VIDEOS, $DATA, $AUDIOS) + SELECT id, conversation_id, name, category, media_url, media_mine_type + FROM messages WHERE category IN ($IMAGES, $VIDEOS, $DATA, $AUDIOS) AND media_status = 'DONE' AND rowid <= :rowId LIMIT :limit OFFSET :offset """, ) @@ -629,7 +629,7 @@ interface MessageDao : BaseDao { // DELETE COUNT @Query( """ - SELECT count(id) FROM messages + SELECT count(id) FROM messages WHERE conversation_id = :conversationId AND media_status = 'DONE' AND category IN (:signalCategory, :plainCategory, :encryptedCategory) """, ) @@ -650,9 +650,9 @@ interface MessageDao : BaseDao { u.full_name AS userFullName, u.identity_number AS userIdentityNumber, m.category AS type, m.content AS content, m.created_at AS createdAt, m.status AS status, m.media_status AS mediaStatus, m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.name AS mediaName, m.media_size AS mediaSize - FROM messages m INNER JOIN users u ON m.user_id = u.user_id + FROM messages m INNER JOIN users u ON m.user_id = u.user_id WHERE m.conversation_id = :conversationId - AND (m.category IN ($DATA)) + AND (m.category IN ($DATA)) AND m.media_mime_type LIKE 'audio%' AND m.media_status != 'EXPIRED' ORDER BY m.created_at ASC, m.rowid ASC @@ -664,8 +664,8 @@ interface MessageDao : BaseDao { @Query( """ SELECT count(1) FROM messages - WHERE conversation_id = :conversationId - AND category IN ($DATA) + WHERE conversation_id = :conversationId + AND category IN ($DATA) AND media_mime_type LIKE 'audio%' AND media_status != 'EXPIRED' AND created_at < (SELECT created_at FROM messages WHERE id = :messageId) @@ -684,7 +684,7 @@ interface MessageDao : BaseDao { u.full_name AS userFullName, u.identity_number AS userIdentityNumber, m.category AS type, m.content AS content, m.created_at AS createdAt, m.status AS status, m.media_status AS mediaStatus, m.media_url AS mediaUrl, m.media_mime_type AS mediaMimeType, m.name AS mediaName, m.media_size AS mediaSize - FROM messages m INNER JOIN users u ON m.user_id = u.user_id + FROM messages m INNER JOIN users u ON m.user_id = u.user_id WHERE m.conversation_id = :conversationId AND m.id IN (:ids) """, @@ -739,9 +739,9 @@ interface MessageDao : BaseDao { @Query( """ - UPDATE messages SET category = 'MESSAGE_RECALL', content = NULL, media_url = NULL, media_mime_type = NULL, media_size = NULL, - media_duration = NULL, media_width = NULL, media_height = NULL, media_hash = NULL, thumb_image = NULL, media_key = NULL, - media_digest = NUll, media_status = NULL, `action` = NULL, participant_id = NULL, snapshot_id = NULL, hyperlink = NULL, name = NULL, + UPDATE messages SET category = 'MESSAGE_RECALL', content = NULL, media_url = NULL, media_mime_type = NULL, media_size = NULL, + media_duration = NULL, media_width = NULL, media_height = NULL, media_hash = NULL, thumb_image = NULL, media_key = NULL, + media_digest = NUll, media_status = NULL, `action` = NULL, participant_id = NULL, snapshot_id = NULL, hyperlink = NULL, name = NULL, album_id = NULL, sticker_id = NULL, shared_user_id = NULL, media_waveform = NULL, quote_message_id = NULL, quote_content = NULL WHERE id = :id """, ) @@ -805,10 +805,10 @@ interface MessageDao : BaseDao { @Query( """ - UPDATE messages SET content = :content, media_mime_type = :mediaMimeType, - media_size = :mediaSize, media_width = :mediaWidth, media_height = :mediaHeight, - thumb_image = :thumbImage, media_key = :mediaKey, media_digest = :mediaDigest, media_duration = :mediaDuration, - media_status = :mediaStatus, status = :status, name = :name, media_waveform = :mediaWaveform WHERE id = :messageId + UPDATE messages SET content = :content, media_mime_type = :mediaMimeType, + media_size = :mediaSize, media_width = :mediaWidth, media_height = :mediaHeight, + thumb_image = :thumbImage, media_key = :mediaKey, media_digest = :mediaDigest, media_duration = :mediaDuration, + media_status = :mediaStatus, status = :status, name = :name, media_waveform = :mediaWaveform WHERE id = :messageId AND category != 'MESSAGE_RECALL' """, ) @@ -845,7 +845,7 @@ interface MessageDao : BaseDao { @Query( """ - UPDATE messages SET media_width = :width, media_height = :height, media_url=:url, thumb_url = :thumbUrl, status = :status + UPDATE messages SET media_width = :width, media_height = :height, media_url=:url, thumb_url = :thumbUrl, status = :status WHERE id = :messageId AND category != 'MESSAGE_RECALL' """, ) @@ -860,7 +860,7 @@ interface MessageDao : BaseDao { @Query( """ - UPDATE messages SET content = :content, media_size = :mediaSize, media_status = :mediaStatus, status = :status + UPDATE messages SET content = :content, media_size = :mediaSize, media_status = :mediaStatus, status = :status WHERE id = :messageId AND category != 'MESSAGE_RECALL' """, ) diff --git a/app/src/main/java/one/mixin/android/db/MixinDatabase.kt b/app/src/main/java/one/mixin/android/db/MixinDatabase.kt index f4cb915ad0..c06d22fba7 100644 --- a/app/src/main/java/one/mixin/android/db/MixinDatabase.kt +++ b/app/src/main/java/one/mixin/android/db/MixinDatabase.kt @@ -70,6 +70,7 @@ import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_66_67 import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_67_68 import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_68_69 import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_69_70 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_70_71 import one.mixin.android.db.converter.DepositEntryListConverter import one.mixin.android.db.converter.FiatOrderConverter @@ -77,6 +78,7 @@ import one.mixin.android.db.converter.MembershipConverter import one.mixin.android.db.converter.MessageStatusConverter import one.mixin.android.db.converter.OutputStateConverter import one.mixin.android.db.converter.PriceListConverter +import one.mixin.android.db.converter.RawTransactionStateConverter import one.mixin.android.db.converter.RawTransactionTypeConverter import one.mixin.android.db.converter.SafeDepositConverter import one.mixin.android.db.converter.SafeWithdrawalConverter @@ -208,6 +210,7 @@ import kotlin.math.min SafeDepositConverter::class, SafeWithdrawalConverter::class, RawTransactionTypeConverter::class, + RawTransactionStateConverter::class, OutputStateConverter::class, TreasuryConverter::class, PriceListConverter::class, @@ -427,6 +430,7 @@ abstract class MixinDatabase : RoomDatabase() { MIGRATION_67_68, MIGRATION_68_69, MIGRATION_69_70, + MIGRATION_70_71, ) .enableMultiInstanceInvalidation() .setQueryExecutor( diff --git a/app/src/main/java/one/mixin/android/db/MixinDatabaseMigrations.kt b/app/src/main/java/one/mixin/android/db/MixinDatabaseMigrations.kt index 97afb771b6..12cdfa3ea9 100644 --- a/app/src/main/java/one/mixin/android/db/MixinDatabaseMigrations.kt +++ b/app/src/main/java/one/mixin/android/db/MixinDatabaseMigrations.kt @@ -604,6 +604,14 @@ class MixinDatabaseMigrations private constructor() { } } + val MIGRATION_70_71: Migration = + object : Migration(70, 71) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("UPDATE raw_transactions SET state = 'spent' WHERE state = 'signed'") + db.execSQL("UPDATE raw_transactions SET state = 'signed' WHERE state = 'unspent'") + } + } + // If you add a new table, be sure to add a clear method to the DatabaseUtil } } diff --git a/app/src/main/java/one/mixin/android/db/RawTransactionDao.kt b/app/src/main/java/one/mixin/android/db/RawTransactionDao.kt index 49a849f4bb..91a08dbd8f 100644 --- a/app/src/main/java/one/mixin/android/db/RawTransactionDao.kt +++ b/app/src/main/java/one/mixin/android/db/RawTransactionDao.kt @@ -6,11 +6,11 @@ import one.mixin.android.vo.safe.RawTransaction @Dao interface RawTransactionDao : BaseDao { - @Query("SELECT * FROM raw_transactions WHERE state = 'unspent' AND (type = 0 OR type = 1) ORDER BY rowid ASC LIMIT 1") - fun findUnspentTransaction(): RawTransaction? + @Query("SELECT * FROM raw_transactions WHERE state = 'signed' AND (type = 0 OR type = 1) ORDER BY rowid ASC LIMIT 1") + fun findSignedTransaction(): RawTransaction? - @Query("SELECT count(1) FROM raw_transactions WHERE state = 'unspent' AND (type = 0 OR type = 1)") - suspend fun countUnspentTransaction(): Int + @Query("SELECT count(1) FROM raw_transactions WHERE state = 'signed' AND (type = 0 OR type = 1)") + suspend fun countSignedTransaction(): Int @Query("SELECT * FROM raw_transactions WHERE request_id = :requestId") fun findRawTransaction(requestId: String): RawTransaction? diff --git a/app/src/main/java/one/mixin/android/db/converter/RawTransactionStateConverter.kt b/app/src/main/java/one/mixin/android/db/converter/RawTransactionStateConverter.kt new file mode 100644 index 0000000000..5068c78ac1 --- /dev/null +++ b/app/src/main/java/one/mixin/android/db/converter/RawTransactionStateConverter.kt @@ -0,0 +1,17 @@ +package one.mixin.android.db.converter + +import androidx.room.TypeConverter +import one.mixin.android.vo.safe.RawTransactionState +import java.util.Locale + +class RawTransactionStateConverter { + @TypeConverter + fun toRawTransactionState(value: String): RawTransactionState { + return RawTransactionState.valueOf(value.lowercase(Locale.US)) + } + + @TypeConverter + fun fromRawTransactionState(state: RawTransactionState): String { + return state.name.lowercase(Locale.US) + } +} diff --git a/app/src/main/java/one/mixin/android/job/RestoreTransactionJob.kt b/app/src/main/java/one/mixin/android/job/RestoreTransactionJob.kt index 5ccfa1fa2c..6b532eea8d 100644 --- a/app/src/main/java/one/mixin/android/job/RestoreTransactionJob.kt +++ b/app/src/main/java/one/mixin/android/job/RestoreTransactionJob.kt @@ -21,7 +21,7 @@ import one.mixin.android.vo.createConversation import one.mixin.android.vo.createMessage import one.mixin.android.vo.generateConversationId import one.mixin.android.vo.notMessengerUser -import one.mixin.android.vo.safe.OutputState +import one.mixin.android.vo.safe.RawTransactionState import one.mixin.android.vo.safe.RawTransactionType import one.mixin.android.vo.safe.SafeSnapshotType import timber.log.Timber @@ -44,7 +44,7 @@ class RestoreTransactionJob : BaseJob( ) { while (true) { val transaction = - rawTransactionDao.findUnspentTransaction() ?: return@runBlocking + rawTransactionDao.findSignedTransaction() ?: return@runBlocking val feeTraceId = uniqueObjectId(transaction.requestId, "FEE") val feeTransaction = rawTransactionDao.findRawTransaction(feeTraceId, RawTransactionType.FEE.value) @@ -55,9 +55,9 @@ class RestoreTransactionJob : BaseJob( Timber.e("Restore Transaction(${transaction.requestId}): db begin") appDatabase.runInTransaction { Timber.e("Restore Transaction(${transaction.requestId}): update raw transaction ${transaction.requestId}") - rawTransactionDao.updateRawTransaction(transaction.requestId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(transaction.requestId, RawTransactionState.spent.name) Timber.e("Restore Transaction(${transaction.requestId}): update raw transaction $feeTraceId") - rawTransactionDao.updateRawTransaction(feeTraceId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) } Timber.e("Restore Transaction(${transaction.requestId}): db end") if (feeTransaction == null) { @@ -82,9 +82,9 @@ class RestoreTransactionJob : BaseJob( Timber.e("Restore Transaction(${transaction.requestId}): db begin") appDatabase.runInTransaction { Timber.e("Restore Transaction(${transaction.requestId}): update raw transaction ${transaction.requestId}") - rawTransactionDao.updateRawTransaction(transaction.requestId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(transaction.requestId, RawTransactionState.spent.name) Timber.e("Restore Transaction(${transaction.requestId}): update raw transaction $feeTraceId") - rawTransactionDao.updateRawTransaction(feeTraceId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) } Timber.e("Restore Transaction(${transaction.requestId}): db end") if (feeTransaction == null && transaction.receiverId.isNotBlank()) { @@ -93,8 +93,8 @@ class RestoreTransactionJob : BaseJob( } else { Timber.e("Restore Transaction(${transaction.requestId}): Post Transaction Error ${transactionRsp.errorDescription}") reportException(e = Throwable("Transaction Error ${transactionRsp.errorDescription}")) - rawTransactionDao.updateRawTransaction(transaction.requestId, OutputState.signed.name) - rawTransactionDao.updateRawTransaction(feeTraceId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(transaction.requestId, RawTransactionState.spent.name) + rawTransactionDao.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) } jobManager.addJobInBackground(SyncOutputJob()) } else if (response.errorCode >= 500) { diff --git a/app/src/main/java/one/mixin/android/repository/TokenRepository.kt b/app/src/main/java/one/mixin/android/repository/TokenRepository.kt index e9c6720bdb..1ef9ad18b5 100644 --- a/app/src/main/java/one/mixin/android/repository/TokenRepository.kt +++ b/app/src/main/java/one/mixin/android/repository/TokenRepository.kt @@ -1007,7 +1007,7 @@ class TokenRepository suspend fun removeUtxo(outputId: String) = outputDao.removeUtxo(outputId) - fun firstUnspentTransaction() = rawTransactionDao.findUnspentTransaction() + fun firstSignedTransaction() = rawTransactionDao.findSignedTransaction() suspend fun findLastWithdrawalSnapshotByReceiver(formatDestination: String) = safeSnapshotDao.findLastWithdrawalSnapshotByReceiver(formatDestination) diff --git a/app/src/main/java/one/mixin/android/ui/common/BottomSheetViewModel.kt b/app/src/main/java/one/mixin/android/ui/common/BottomSheetViewModel.kt index 4ca2568de3..873ef4e854 100644 --- a/app/src/main/java/one/mixin/android/ui/common/BottomSheetViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/common/BottomSheetViewModel.kt @@ -131,6 +131,7 @@ import one.mixin.android.vo.notMessengerUser import one.mixin.android.vo.safe.Output import one.mixin.android.vo.safe.OutputState import one.mixin.android.vo.safe.RawTransaction +import one.mixin.android.vo.safe.RawTransactionState import one.mixin.android.vo.safe.RawTransactionType import one.mixin.android.vo.safe.SafeSnapshot import one.mixin.android.vo.safe.SafeSnapshotType @@ -371,9 +372,9 @@ class BottomSheetViewModel Timber.e("Kernel Withdrawal($traceId): db insert fee snapshot") tokenRepository.insertSafeSnapshot(UUID.nameUUIDFromBytes("$senderId:$feeTransactionHash".toByteArray()).toString(), senderId, receiverId, feeTransactionHash, feeTraceId, feeAssetId, feeAmount, "", SafeSnapshotType.snapshot) Timber.e("Kernel Withdrawal($traceId): db raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(withdrawalData.requestId, signWithdrawalResult.raw, formatDestination(destination, tag), RawTransactionType.WITHDRAWAL, OutputState.unspent, nowInUtc(), withdrawalUtxos.inscriptionHash)) + tokenRepository.insetRawTransaction(RawTransaction(withdrawalData.requestId, signWithdrawalResult.raw, formatDestination(destination, tag), RawTransactionType.WITHDRAWAL, RawTransactionState.signed, nowInUtc(), withdrawalUtxos.inscriptionHash)) Timber.e("Kernel Withdrawal($traceId): db insert fee raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(feeData.requestId, signFeeResult.raw, receiverId, RawTransactionType.FEE, OutputState.unspent, nowInUtc(), null)) + tokenRepository.insetRawTransaction(RawTransaction(feeData.requestId, signFeeResult.raw, receiverId, RawTransactionType.FEE, RawTransactionState.signed, nowInUtc(), null)) } Timber.e("Kernel Withdrawal($traceId): db end") jobManager.addJobInBackground(CheckBalanceJob(arrayListOf(assetIdToAsset(assetId), assetIdToAsset(feeAssetId)))) @@ -406,7 +407,7 @@ class BottomSheetViewModel ), ) Timber.e("Kernel Withdrawal($traceId): db update raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(withdrawalData.requestId, signWithdrawalResult.raw, formatDestination(destination, tag), RawTransactionType.WITHDRAWAL, OutputState.unspent, nowInUtc(), withdrawalUtxos.inscriptionHash)) + tokenRepository.insetRawTransaction(RawTransaction(withdrawalData.requestId, signWithdrawalResult.raw, formatDestination(destination, tag), RawTransactionType.WITHDRAWAL, RawTransactionState.signed, nowInUtc(), withdrawalUtxos.inscriptionHash)) } Timber.e("Kernel Withdrawal($traceId): db end") jobManager.addJobInBackground(CheckBalanceJob(arrayListOf(assetIdToAsset(assetId)))) @@ -422,12 +423,12 @@ class BottomSheetViewModel if (transactionRsp.error != null) { Timber.e("Kernel Withdrawal($traceId): withdrawal error ${transactionRsp.errorDescription}") reportException(Throwable("Transaction Error ${transactionRsp.errorDescription}")) - tokenRepository.updateRawTransaction(traceId, OutputState.signed.name) - tokenRepository.updateRawTransaction(feeTraceId, OutputState.signed.name) + tokenRepository.updateRawTransaction(traceId, RawTransactionState.spent.name) + tokenRepository.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) return transactionRsp } else { - tokenRepository.updateRawTransaction(traceId, OutputState.signed.name) - tokenRepository.updateRawTransaction(feeTraceId, OutputState.signed.name) + tokenRepository.updateRawTransaction(traceId, RawTransactionState.spent.name) + tokenRepository.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) } jobManager.addJobInBackground(SyncOutputJob()) Timber.e("Kernel Withdrawal($traceId): withdrawal end") @@ -452,7 +453,7 @@ class BottomSheetViewModel Timber.e("Kernel Address Transaction($trace): begin") val rawTransaction = tokenRepository.findRawTransaction(trace) - if (rawTransaction?.state == OutputState.unspent) { + if (rawTransaction?.state == RawTransactionState.signed) { Timber.e("Kernel Address Transaction($trace): sync restore") return innerTransaction(rawTransaction.rawTransaction, trace, listOf(), utxoWrapper.inscriptionHash) } @@ -501,7 +502,7 @@ class BottomSheetViewModel Timber.e("Kernel Address Transaction($trace): sign db insert snapshot") tokenRepository.insertSafeSnapshot(UUID.nameUUIDFromBytes("$senderId:$transactionHash".toByteArray()).toString(), senderId, kernelAddress, transactionHash, trace, assetId, amount, memo, SafeSnapshotType.snapshot, reference = reference) Timber.e("Kernel Address Transaction($trace): sign db insert raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(transactionResponse.data!!.first().requestId, signResult.raw, "", RawTransactionType.TRANSFER, OutputState.unspent, nowInUtc(), utxoWrapper.inscriptionHash)) + tokenRepository.insetRawTransaction(RawTransaction(transactionResponse.data!!.first().requestId, signResult.raw, "", RawTransactionType.TRANSFER, RawTransactionState.signed, nowInUtc(), utxoWrapper.inscriptionHash)) Timber.e("Kernel Address Transaction($trace): sign db mark utxo ${utxoWrapper.ids.joinToString(", ")}") tokenRepository.updateUtxoToSigned(utxoWrapper.ids) Timber.e("Kernel Address Transaction: sign end") @@ -592,7 +593,7 @@ class BottomSheetViewModel tokenRepository.insertSafeSnapshot(UUID.nameUUIDFromBytes("${senderIds.first()}:$transactionHash".toByteArray()).toString(), senderIds.first(), opponentId, transactionHash, trace, assetId, amount, memo, SafeSnapshotType.snapshot, reference = reference ?: (if (release == true) null else inscriptionHash)) } Timber.e("Kernel Transaction($trace): sign db insert raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(transactionResponse.data!!.first().requestId, signResult.raw, receiverIds.joinToString(","), RawTransactionType.TRANSFER, OutputState.unspent, nowInUtc(), if (release == true) null else utxoWrapper.inscriptionHash)) + tokenRepository.insetRawTransaction(RawTransaction(transactionResponse.data!!.first().requestId, signResult.raw, receiverIds.joinToString(","), RawTransactionType.TRANSFER, RawTransactionState.signed, nowInUtc(), if (release == true) null else utxoWrapper.inscriptionHash)) Timber.e("Kernel Transaction($trace): sign db mark utxo ${utxoWrapper.ids.joinToString(", ")}") tokenRepository.updateUtxoToSigned(utxoWrapper.ids) Timber.e("Kernel Transaction: sign end") @@ -622,11 +623,11 @@ class BottomSheetViewModel if (transactionRsp.error != null) { Timber.e("Kernel Transaction($traceId): innerTransaction error ${transactionRsp.errorDescription}") reportException(Throwable("Transaction Error ${transactionRsp.errorDescription}")) - tokenRepository.updateRawTransaction(transactionRsp.data!!.first().requestId, OutputState.signed.name) + tokenRepository.updateRawTransaction(transactionRsp.data!!.first().requestId, RawTransactionState.spent.name) return transactionRsp } else { Timber.e("Kernel Transaction($traceId): innerTransaction update raw transaction") - tokenRepository.updateRawTransaction(transactionRsp.data!!.first().requestId, OutputState.signed.name) + tokenRepository.updateRawTransaction(transactionRsp.data!!.first().requestId, RawTransactionState.spent.name) } if (receiverIds.size == 1 && !isConsolidation) { // Workaround with only the case of a single transfer @@ -813,7 +814,7 @@ class BottomSheetViewModel "" } else { recipient.uuidMembers.joinToString(",") - }, RawTransactionType.TRANSFER, OutputState.unspent, nowInUtc(), null + }, RawTransactionType.TRANSFER, RawTransactionState.signed, nowInUtc(), null ) ) Timber.e("Kernel Duplicate Invoice Transaction(${signedTransaction.trace}): sign db mark utxo ${signedTransaction.utxoWrapperIds.joinToString(", ")}") @@ -830,7 +831,7 @@ class BottomSheetViewModel signedResponse.data?.forEach { tokenRepository.updateRawTransaction( it.requestId, - OutputState.signed.name + RawTransactionState.spent.name ) } Timber.e("Kernel Duplicate Invoice Transaction(${signedTransaction.trace}): sign db end") @@ -1029,7 +1030,7 @@ class BottomSheetViewModel "" } else { invoice.recipient.uuidMembers.joinToString(",") - }, RawTransactionType.TRANSFER, OutputState.unspent, nowInUtc(), null + }, RawTransactionType.TRANSFER, RawTransactionState.signed, nowInUtc(), null ) ) Timber.e("Kernel Invoice Transaction(${t.trace}): sign db mark utxo ${t.utxoWrapperIds.joinToString(", ")}") @@ -1049,7 +1050,7 @@ class BottomSheetViewModel appDatabase.runInTransaction { Timber.e("Kernel Invoice Transaction: sign db begin") signedResponse.data?.forEach { - tokenRepository.updateRawTransaction(it.requestId, OutputState.signed.name) + tokenRepository.updateRawTransaction(it.requestId, RawTransactionState.spent.name) } Timber.e("Kernel Invoice Transaction: sign db end") } @@ -1757,9 +1758,9 @@ class BottomSheetViewModel } } - suspend fun firstUnspentTransaction() = + suspend fun firstSignedTransaction() = withContext(Dispatchers.IO) { - tokenRepository.firstUnspentTransaction() + tokenRepository.firstSignedTransaction() } suspend fun getScheme(id: String) = accountRepository.getScheme(id) diff --git a/app/src/main/java/one/mixin/android/ui/conversation/link/parser/NewSchemeParser.kt b/app/src/main/java/one/mixin/android/ui/conversation/link/parser/NewSchemeParser.kt index 4ce4bd9028..ba5b431302 100644 --- a/app/src/main/java/one/mixin/android/ui/conversation/link/parser/NewSchemeParser.kt +++ b/app/src/main/java/one/mixin/android/ui/conversation/link/parser/NewSchemeParser.kt @@ -167,7 +167,7 @@ class NewSchemeParser( return Result.failure(ParserError(FAILURE)) } } - val rawTransaction = linkViewModel.firstUnspentTransaction() + val rawTransaction = linkViewModel.firstSignedTransaction() if (rawTransaction != null) { Timber.d("$TAG invoice found pending raw transaction traceId=$traceId") WaitingBottomSheetDialogFragment.newInstance().showNow(bottomSheet.parentFragmentManager, WaitingBottomSheetDialogFragment.TAG) @@ -507,7 +507,7 @@ class NewSchemeParser( } private suspend fun checkRawTransaction(biometricItem: AssetBiometricItem) { - val rawTransaction = linkViewModel.firstUnspentTransaction() + val rawTransaction = linkViewModel.firstSignedTransaction() if (rawTransaction != null) { Timber.d("$TAG checkRawTransaction found pending raw transaction ${describeBiometricItem(biometricItem)}") WaitingBottomSheetDialogFragment.newInstance().showNow(bottomSheet.parentFragmentManager, WaitingBottomSheetDialogFragment.TAG) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/Web3ViewModel.kt b/app/src/main/java/one/mixin/android/ui/home/web3/Web3ViewModel.kt index 6e9227381d..be87c4c43b 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/Web3ViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/Web3ViewModel.kt @@ -545,9 +545,9 @@ class Web3ViewModel @Inject constructor( } - suspend fun firstUnspentTransaction() = + suspend fun firstSignedTransaction() = withContext(Dispatchers.IO) { - tokenRepository.firstUnspentTransaction() + tokenRepository.firstSignedTransaction() } suspend fun findLatestTrace( diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/InputArea.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/InputArea.kt index c9852105b6..13ae090afa 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/InputArea.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/InputArea.kt @@ -60,6 +60,7 @@ fun InputArea( displayBalanceOverride: String? = null, bottomCompose: (@Composable () -> Unit)? = null, inlineEndCompose: (@Composable () -> Unit)? = null, + maxDecimalPlaces: Int? = null, ) { val viewModel = hiltViewModel() val balance = if (token == null) { @@ -92,7 +93,15 @@ fun InputArea( } } Box(modifier = Modifier.height(10.dp)) - InputContent(token = token, text = text, selectClick = selectClick, onInputChanged = onInputChanged, readOnly = readOnly, inlineEndCompose = inlineEndCompose) + InputContent( + token = token, + text = text, + selectClick = selectClick, + onInputChanged = onInputChanged, + readOnly = readOnly, + inlineEndCompose = inlineEndCompose, + maxDecimalPlaces = maxDecimalPlaces, + ) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { token?.let { t -> val depositVisible = !readOnly && onDeposit != null && (balance?.toBigDecimalOrNull()?.compareTo(BigDecimal.ZERO) ?: 0) == 0 diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt index ee0a026909..9f0a9f3dec 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt @@ -33,6 +33,7 @@ import one.mixin.android.api.response.web3.QuoteResult import one.mixin.android.api.response.web3.SwapToken import one.mixin.android.compose.theme.MixinAppTheme import one.mixin.android.ui.home.web3.trade.SwapViewModel +import one.mixin.android.ui.home.web3.trade.tradePriceInputMaxDecimalPlaces import java.math.BigDecimal import java.math.RoundingMode @@ -175,6 +176,7 @@ fun PriceInputArea( title = stringResource(id = R.string.limit_price, priceDisplayState.displayChainName), readOnly = false, selectClick = null, + maxDecimalPlaces = tradePriceInputMaxDecimalPlaces(), onInputChanged = { userInput -> displayPrice = userInput val inputPrice = userInput.toBigDecimalOrNull() diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/InputTextField.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/InputTextField.kt index 03c17b26f2..979483e072 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/InputTextField.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/InputTextField.kt @@ -55,6 +55,47 @@ import one.mixin.android.ui.home.inscription.component.AutoSizeText import one.mixin.android.widget.CoilRoundedHexagonTransformation import java.math.BigDecimal +internal const val TRADE_INPUT_MAX_DECIMAL_PLACES = 8 + +internal fun tradePriceInputMaxDecimalPlaces(): Int = TRADE_INPUT_MAX_DECIMAL_PLACES + +internal fun tradeInputMaxDecimalPlaces( + isCommonWallet: Boolean, + precision: Int, +): Int { + return if (isCommonWallet && precision >= 0) { + precision + } else { + TRADE_INPUT_MAX_DECIMAL_PLACES + } +} + +internal fun SwapToken?.tradeInputMaxDecimalPlaces(): Int { + return this?.let { token -> + tradeInputMaxDecimalPlaces(token.walletId != null, token.decimals) + } ?: TRADE_INPUT_MAX_DECIMAL_PLACES +} + +internal fun isTradeInputDecimalAllowed( + value: String, + maxDecimalPlaces: Int? = TRADE_INPUT_MAX_DECIMAL_PLACES, +): Boolean { + maxDecimalPlaces ?: return true + val decimalIndex = value.indexOf('.') + return decimalIndex < 0 || value.length - decimalIndex - 1 <= maxDecimalPlaces +} + +internal fun limitTradeInputDecimalPlaces( + value: String, + maxDecimalPlaces: Int? = TRADE_INPUT_MAX_DECIMAL_PLACES, +): String { + maxDecimalPlaces ?: return value + val decimalIndex = value.indexOf('.') + if (decimalIndex < 0) return value + val endIndex = (decimalIndex + 1 + maxDecimalPlaces).coerceAtMost(value.length) + return value.substring(0, endIndex) +} + @SuppressLint("UnrememberedMutableState") @Composable fun InputContent( @@ -68,6 +109,7 @@ fun InputContent( inputFontSize: TextUnit = 24.sp, inputFontWeight: FontWeight = FontWeight.Black, autoFocus: Boolean = false, + maxDecimalPlaces: Int? = null, ) { if (readOnly) { Column(modifier = Modifier.fillMaxWidth()) { @@ -136,8 +178,11 @@ fun InputContent( BasicTextField( value = textFieldValue, onValueChange = { + if (!isTradeInputDecimalAllowed(it.text, maxDecimalPlaces)) { + return@BasicTextField + } textFieldValue = it - val v = try { + try { if (it.text.isBlank()) BigDecimal.ZERO else BigDecimal(it.text) } catch (e: Exception) { return@BasicTextField diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt index cc28745cc0..6393ece5ed 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt @@ -153,14 +153,6 @@ fun LimitOrderContent( val viewModel = hiltViewModel() - var inputText by remember { mutableStateOf(initialAmount ?: "") } - var outputText by remember { mutableStateOf("") } - - LaunchedEffect(lastOrderTime) { - inputText = initialAmount ?: "" - outputText = "" - } - var limitPriceText by remember { mutableStateOf("") } var marketPriceClickTime by remember { mutableStateOf(lastOrderTime) } var priceMultiplier by remember { mutableStateOf(null) } @@ -177,6 +169,18 @@ fun LimitOrderContent( var toToken by remember(from, to, isReverse) { mutableStateOf(if (isReverse) from else to) } + val fromMaxDecimalPlaces = fromToken.tradeInputMaxDecimalPlaces() + val toMaxDecimalPlaces = toToken.tradeInputMaxDecimalPlaces() + + var inputText by remember { + mutableStateOf(limitTradeInputDecimalPlaces(initialAmount ?: "", fromMaxDecimalPlaces)) + } + var outputText by remember { mutableStateOf("") } + + LaunchedEffect(lastOrderTime, fromMaxDecimalPlaces) { + inputText = limitTradeInputDecimalPlaces(initialAmount ?: "", fromMaxDecimalPlaces) + outputText = "" + } var isButtonEnabled by remember { mutableStateOf(true) } var isSubmitting by remember { mutableStateOf(false) } @@ -217,7 +221,7 @@ fun LimitOrderContent( if (fromAmount != null && standardPrice != null && fromAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val toAmount = fromAmount.multiply(standardPrice).setScale(8, RoundingMode.DOWN) - outputText = toAmount.stripTrailingZeros().toPlainString() + outputText = limitTradeInputDecimalPlaces(toAmount.stripTrailingZeros().toPlainString(), toMaxDecimalPlaces) } else { outputText = "" } @@ -273,7 +277,8 @@ fun LimitOrderContent( .clickable { AnalyticsTracker.trackSpotSwitchSendReceive() isReverse = !isReverse - inputText = outputText + val nextFromMaxDecimalPlaces = toToken.tradeInputMaxDecimalPlaces() + inputText = limitTradeInputDecimalPlaces(outputText, nextFromMaxDecimalPlaces) val oldPrice = limitPriceText.toBigDecimalOrNull() if (oldPrice != null && oldPrice > BigDecimal.ZERO) { @@ -322,20 +327,20 @@ fun LimitOrderContent( val standardPrice = limitPriceText.toBigDecimalOrNull() if (fromAmount != null && standardPrice != null && fromAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedOutput = fromAmount.multiply(standardPrice).setScale(8, RoundingMode.DOWN) - outputText = calculatedOutput.stripTrailingZeros().toPlainString() + outputText = limitTradeInputDecimalPlaces(calculatedOutput.stripTrailingZeros().toPlainString(), toMaxDecimalPlaces) } else if (fromAmount == null || fromAmount == BigDecimal.ZERO) { outputText = "" } } - }, onDeposit = onDeposit, displayBalanceOverride = if (it.isNativeSolAsset()) fromBalance else null, onMax = { + }, onDeposit = onDeposit, displayBalanceOverride = if (it.isNativeSolAsset()) fromBalance else null, maxDecimalPlaces = fromMaxDecimalPlaces, onMax = { AnalyticsTracker.trackSpotSendInputBalance() - inputText = formatBalanceInput(availableFromBalance, fromToken?.isWeb3 == true) + inputText = limitTradeInputDecimalPlaces(formatBalanceInput(availableFromBalance, fromToken?.isWeb3 == true), fromMaxDecimalPlaces) if (inputText.isNotBlank()) { val fromAmount = inputText.toBigDecimalOrNull() val standardPrice = limitPriceText.toBigDecimalOrNull() if (fromAmount != null && standardPrice != null && fromAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedOutput = fromAmount.multiply(standardPrice).setScale(8, RoundingMode.DOWN) - outputText = calculatedOutput.stripTrailingZeros().toPlainString() + outputText = limitTradeInputDecimalPlaces(calculatedOutput.stripTrailingZeros().toPlainString(), toMaxDecimalPlaces) } else if (fromAmount == null || fromAmount == BigDecimal.ZERO) { outputText = "" } @@ -366,21 +371,22 @@ fun LimitOrderContent( val standardPrice = limitPriceText.toBigDecimalOrNull() if (toAmount != null && standardPrice != null && toAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedInput = toAmount.divide(standardPrice, 8, RoundingMode.DOWN) - inputText = calculatedInput.stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces(calculatedInput.stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else if (toAmount == null || toAmount == BigDecimal.ZERO) { inputText = "" } } }, onDeposit = null, + maxDecimalPlaces = toMaxDecimalPlaces, onMax = { - outputText = formatBalanceInput(toBalance, toToken?.isWeb3 == true) + outputText = limitTradeInputDecimalPlaces(formatBalanceInput(toBalance, toToken?.isWeb3 == true), toMaxDecimalPlaces) if (outputText.isNotBlank()) { val toAmount = outputText.toBigDecimalOrNull() val standardPrice = limitPriceText.toBigDecimalOrNull() if (toAmount != null && standardPrice != null && toAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedInput = toAmount.divide(standardPrice, 8, RoundingMode.DOWN) - inputText = calculatedInput.stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces(calculatedInput.stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else if (toAmount == null || toAmount == BigDecimal.ZERO) { inputText = "" } @@ -632,12 +638,13 @@ fun LimitOrderContent( AnalyticsTracker.trackSpotPriceInputPercent(label) }, onSetInput = { - inputText = it - val fromAmount = it.toBigDecimalOrNull() + val limitedInput = limitTradeInputDecimalPlaces(it, fromMaxDecimalPlaces) + inputText = limitedInput + val fromAmount = limitedInput.toBigDecimalOrNull() val standardPrice = limitPriceText.toBigDecimalOrNull() if (fromAmount != null && standardPrice != null && fromAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedOutput = fromAmount.multiply(standardPrice).setScale(8, RoundingMode.DOWN) - outputText = calculatedOutput.stripTrailingZeros().toPlainString() + outputText = limitTradeInputDecimalPlaces(calculatedOutput.stripTrailingZeros().toPlainString(), toMaxDecimalPlaces) } else if (fromAmount == null || fromAmount == BigDecimal.ZERO) { outputText = "" } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapContent.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapContent.kt index c7805c4652..d8a55cd60c 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapContent.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapContent.kt @@ -113,11 +113,6 @@ fun SwapContent( var quoteMin by remember { mutableStateOf(null) } var quoteMax by remember { mutableStateOf(null) } - var inputText by remember { mutableStateOf(initialAmount ?: "") } - LaunchedEffect(lastOrderTime) { - inputText = initialAmount ?: "" - } - var isLoading by remember { mutableStateOf(false) } var isReverse by remember { mutableStateOf(false) } var invalidFlag by remember { mutableStateOf(false) } @@ -128,6 +123,14 @@ fun SwapContent( var toToken by remember(from, to, isReverse) { mutableStateOf(if (isReverse) from else to) } + val fromMaxDecimalPlaces = fromToken.tradeInputMaxDecimalPlaces() + + var inputText by remember { + mutableStateOf(limitTradeInputDecimalPlaces(initialAmount ?: "", fromMaxDecimalPlaces)) + } + LaunchedEffect(lastOrderTime, fromMaxDecimalPlaces) { + inputText = limitTradeInputDecimalPlaces(initialAmount ?: "", fromMaxDecimalPlaces) + } val shouldRefreshQuote = remember { MutableStateFlow(inputText) } var isButtonEnabled by remember { mutableStateOf(true) } @@ -242,7 +245,8 @@ fun SwapContent( } } quoteResult?.let { - inputText = it.outAmount + val nextFromMaxDecimalPlaces = toToken.tradeInputMaxDecimalPlaces() + inputText = limitTradeInputDecimalPlaces(it.outAmount, nextFromMaxDecimalPlaces) quoteResult = null } context.clickVibrate() @@ -268,11 +272,12 @@ fun SwapContent( onInputChanged = { inputText = it }, onDeposit = onDeposit, displayBalanceOverride = if (from.isNativeSolAsset()) fromBalance else null, + maxDecimalPlaces = fromMaxDecimalPlaces, onMax = { AnalyticsTracker.trackSpotSendInputBalance() val balance = availableFromBalanceValue if (balance > BigDecimal.ZERO) { - inputText = balance.stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces(balance.stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else { inputText = "" } @@ -301,7 +306,9 @@ fun SwapContent( inputText = inputText, quoteMin = quoteMin, quoteMax = quoteMax, - onInputTextChange = { inputText = it }, + onInputTextChange = { + inputText = limitTradeInputDecimalPlaces(it, fromMaxDecimalPlaces) + }, onInvalidFlagChange = { invalidFlag = !invalidFlag }, onSwitchToLimitOrder = onSwitchToLimitOrder, ) @@ -341,7 +348,7 @@ fun SwapContent( InputAction("25%", showBorder = true) { AnalyticsTracker.trackSpotSendInputPercent("25%") if (balance > BigDecimal.ZERO) { - inputText = (balance * BigDecimal("0.25")).stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces((balance * BigDecimal("0.25")).stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else { inputText = "" } @@ -349,7 +356,7 @@ fun SwapContent( InputAction("50%", showBorder = true) { AnalyticsTracker.trackSpotSendInputPercent("50%") if (balance > BigDecimal.ZERO) { - inputText = (balance * BigDecimal("0.5")).stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces((balance * BigDecimal("0.5")).stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else { inputText = "" } @@ -357,7 +364,7 @@ fun SwapContent( InputAction(stringResource(R.string.Max), showBorder = true) { AnalyticsTracker.trackSpotSendInputPercent("max") if (balance > BigDecimal.ZERO) { - inputText = balance.stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces(balance.stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else { inputText = "" } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt index c5a9064938..840fd6112a 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt @@ -1471,7 +1471,8 @@ class TradeFragment : BaseFragment() { private fun getInitialTabIndex(currentWalletId: String): Int { val entryType = arguments?.getString(ARGS_ENTRY_TYPE) - if (entryType == AnalyticsTracker.SpotTradeType.SIMPLE) return TAB_SIMPLE + val entrySource = arguments?.getString(ARGS_ENTRY_SOURCE) + if (entrySource == AnalyticsTracker.TradeSource.MARKET_DETAIL && entryType == AnalyticsTracker.SpotTradeType.SIMPLE) return TAB_SIMPLE val preferenceKey = "$PREF_TRADE_SELECTED_TAB_PREFIX$currentWalletId" return defaultSharedPreferences.getInt(preferenceKey, 0) } diff --git a/app/src/main/java/one/mixin/android/ui/media/SharedMediaViewModel.kt b/app/src/main/java/one/mixin/android/ui/media/SharedMediaViewModel.kt index f977d37dc7..289dd9fee5 100644 --- a/app/src/main/java/one/mixin/android/ui/media/SharedMediaViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/media/SharedMediaViewModel.kt @@ -282,4 +282,3 @@ class ListDataSource(private val items: List) : PositionalDataSource } } } - diff --git a/app/src/main/java/one/mixin/android/ui/wallet/InputFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/InputFragment.kt index c2b9c77c38..63db4dae88 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/InputFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/InputFragment.kt @@ -1814,7 +1814,7 @@ class InputFragment : BaseFragment(R.layout.fragment_input), OnReceiveSelectionC private fun prepareCheck(item: BiometricItem) { viewLifecycleOwner.lifecycleScope.launch { val amount = item.amount - val rawTransaction = web3ViewModel.firstUnspentTransaction() + val rawTransaction = web3ViewModel.firstSignedTransaction() if (rawTransaction != null) { WaitingBottomSheetDialogFragment.newInstance() .showNow(parentFragmentManager, WaitingBottomSheetDialogFragment.TAG) diff --git a/app/src/main/java/one/mixin/android/ui/wallet/MarketDescription.kt b/app/src/main/java/one/mixin/android/ui/wallet/MarketDescription.kt new file mode 100644 index 0000000000..5564dd6bb0 --- /dev/null +++ b/app/src/main/java/one/mixin/android/ui/wallet/MarketDescription.kt @@ -0,0 +1,6 @@ +package one.mixin.android.ui.wallet + +fun selectLocalizedMarketDescription( + descriptions: Map, + language: String, +): String? = descriptions[language]?.takeIf { it.isNotBlank() } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/MarketDetailsFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/MarketDetailsFragment.kt index 5372eebdcc..3769c636ae 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/MarketDetailsFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/MarketDetailsFragment.kt @@ -352,9 +352,7 @@ class MarketDetailsFragment : BaseFragment(R.layout.fragment_details_market) { val desc = info.descriptions?.let { map -> val lang = Locale.getDefault().language - (map[lang]?.takeIf { it.isNotBlank() } - ?: map["en"]?.takeIf { it.isNotBlank() } - ?: map.values.firstOrNull { it.isNotBlank() }) + selectLocalizedMarketDescription(map, lang) }?.let { HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_LEGACY).toString().trim() } aboutContainer.isVisible = !desc.isNullOrBlank() aboutContent.text = desc.orEmpty() diff --git a/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt b/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt index 7ed0852e30..04d840bddb 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt @@ -34,6 +34,7 @@ import one.mixin.android.ui.home.inscription.InscriptionActivity import one.mixin.android.vo.Fiats import one.mixin.android.vo.SnapshotItem import one.mixin.android.vo.Ticker +import one.mixin.android.vo.safe.RawTransaction import one.mixin.android.vo.safe.SafeSnapshotType import one.mixin.android.vo.safe.TokenItem import one.mixin.android.widget.linktext.RoundBackgroundColorSpan @@ -61,7 +62,8 @@ interface TransactionInterface { contentBinding.avatarVa.setOnClickListener { clickAvatar(fragment, asset, snapshot.inscriptionHash) } - updateUI(fragment, contentBinding, asset, snapshot) + val rawTransaction = snapshot.traceId?.let { walletViewModel.findRawTransaction(it) } + updateUI(fragment, contentBinding, asset, snapshot, rawTransaction) fetchThatTimePrice( fragment, lifecycleScope, @@ -77,6 +79,7 @@ interface TransactionInterface { walletViewModel, snapshot, asset, + rawTransaction, ) } } @@ -87,23 +90,27 @@ interface TransactionInterface { contentBinding.avatarVa.setOnClickListener { clickAvatar(fragment, tokenItem, snapshotItem.inscriptionHash) } - updateUI(fragment, contentBinding, tokenItem, snapshotItem) - fetchThatTimePrice( - fragment, - lifecycleScope, - walletViewModel, - contentBinding, - tokenItem.assetId, - snapshotItem, - ) - refreshIncompleteSnapshot( - fragment, - contentBinding, - lifecycleScope, - walletViewModel, - snapshotItem, - tokenItem, - ) + lifecycleScope.launch { + val rawTransaction = snapshotItem.traceId?.let { walletViewModel.findRawTransaction(it) } + updateUI(fragment, contentBinding, tokenItem, snapshotItem, rawTransaction) + fetchThatTimePrice( + fragment, + lifecycleScope, + walletViewModel, + contentBinding, + tokenItem.assetId, + snapshotItem, + ) + refreshIncompleteSnapshot( + fragment, + contentBinding, + lifecycleScope, + walletViewModel, + snapshotItem, + tokenItem, + rawTransaction, + ) + } } } @@ -286,12 +293,14 @@ interface TransactionInterface { contentBinding: FragmentTransactionBinding, asset: TokenItem, snapshot: SnapshotItem, + rawTransaction: RawTransaction? = null, ) { if (checkDestroyed(fragment)) return contentBinding.apply { val amountVal = snapshot.amount.toFloatOrNull() val isPositive = if (amountVal == null) false else amountVal > 0 + val showPendingHash = snapshot.shouldShowPendingHash(rawTransaction) if (snapshot.inscriptionHash.isNullOrEmpty()) { avatarVa.displayedChild = 0 contentBinding.avatar.loadToken(asset) @@ -326,7 +335,7 @@ interface TransactionInterface { val amountColor = fragment.resources.getColor( when { - snapshot.type == SafeSnapshotType.pending.name -> { + snapshot.type == SafeSnapshotType.pending.name || showPendingHash -> { R.color.wallet_text_gray } isPositive -> { @@ -352,6 +361,7 @@ interface TransactionInterface { transactionIdTv.text = snapshot.snapshotId transactionHashLayout.isVisible = !snapshot.transactionHash.isNullOrBlank() transactionHashTv.text = snapshot.transactionHash + hashPendingPb.isVisible = false dateTv.text = snapshot.createdAt.fullDate() memoLl.isVisible = snapshot.formatMemo != null memoTv.text = snapshot.formatMemo?.utf ?: snapshot.formatMemo?.hex @@ -425,6 +435,7 @@ interface TransactionInterface { if (snapshot.withdrawal != null) { hashLl.isVisible = true hashTitle.text = fragment.getString(R.string.withdrawal_hash) + hashPendingPb.isVisible = showPendingHash if (snapshot.withdrawal.withdrawalHash.isBlank()) { hashTv.text = fragment.getString(R.string.withdrawal_pending) } else { @@ -463,12 +474,13 @@ interface TransactionInterface { walletViewModel: WalletViewModel, snapshot: SnapshotItem, asset: TokenItem, + rawTransaction: RawTransaction? = null, ) { if (snapshot.isDataIncomplete()) { lifecycleScope.launch { walletViewModel.refreshSnapshot(snapshot.snapshotId)?.let { it.label = snapshot.label // Saving temporary variables - updateUI(fragment, contentBinding, asset, it) + updateUI(fragment, contentBinding, asset, it, rawTransaction) } } } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt b/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt index 96832c1a90..24b1f5670d 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt @@ -57,6 +57,7 @@ import one.mixin.android.vo.UtxoItem import one.mixin.android.vo.market.Market import one.mixin.android.vo.market.MarketItem import one.mixin.android.vo.safe.Output +import one.mixin.android.vo.safe.RawTransaction import one.mixin.android.vo.safe.SafeSnapshot import one.mixin.android.vo.safe.TokenItem import one.mixin.android.vo.sumsub.ProfileResponse @@ -319,6 +320,11 @@ internal constructor( suspend fun findSnapshot(snapshotId: String): SnapshotItem? = tokenRepository.findSnapshotById(snapshotId) + suspend fun findRawTransaction(traceId: String): RawTransaction? = + withContext(Dispatchers.IO) { + tokenRepository.findRawTransaction(traceId) + } + suspend fun profile(): MixinResponse = tokenRepository.profile() suspend fun fetchSessionsSuspend(ids: List) = userRepository.fetchSessionsSuspend(ids) diff --git a/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt b/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt index d7996d72da..3ec540ea16 100644 --- a/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt +++ b/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt @@ -12,6 +12,8 @@ import kotlinx.serialization.SerialName import one.mixin.android.extension.hexString import one.mixin.android.extension.isByteArrayValidUtf8 import one.mixin.android.extension.isValidHex +import one.mixin.android.vo.safe.RawTransaction +import one.mixin.android.vo.safe.RawTransactionState import one.mixin.android.vo.safe.SafeDeposit import one.mixin.android.vo.safe.SafeSnapshotType import one.mixin.android.vo.safe.SafeWithdrawal @@ -159,6 +161,8 @@ data class SnapshotItem( } fun isPendingWithdrawal() = withdrawal != null && withdrawal.withdrawalHash.isNullOrBlank() + + fun shouldShowPendingHash(rawTransaction: RawTransaction?) = rawTransaction?.state == RawTransactionState.signed } @Parcelize diff --git a/app/src/main/java/one/mixin/android/vo/safe/RawTransaction.kt b/app/src/main/java/one/mixin/android/vo/safe/RawTransaction.kt index 8410c2d80e..7cf437e2a4 100644 --- a/app/src/main/java/one/mixin/android/vo/safe/RawTransaction.kt +++ b/app/src/main/java/one/mixin/android/vo/safe/RawTransaction.kt @@ -22,7 +22,7 @@ data class RawTransaction( @ColumnInfo(name = "type") val type: RawTransactionType, @ColumnInfo(name = "state") - val state: OutputState, + val state: RawTransactionState, @ColumnInfo(name = "created_at") val createdAt: String, @ColumnInfo(name = "inscription_hash") diff --git a/app/src/main/java/one/mixin/android/vo/safe/RawTransactionState.kt b/app/src/main/java/one/mixin/android/vo/safe/RawTransactionState.kt new file mode 100644 index 0000000000..2617ca6f55 --- /dev/null +++ b/app/src/main/java/one/mixin/android/vo/safe/RawTransactionState.kt @@ -0,0 +1,6 @@ +package one.mixin.android.vo.safe + +enum class RawTransactionState { + signed, // Signed, pending broadcast + spent // Broadcast (terminal state) +} diff --git a/app/src/main/res/layout/fragment_transaction.xml b/app/src/main/res/layout/fragment_transaction.xml index a3dc632e0c..3cdfd0f2f6 100644 --- a/app/src/main/res/layout/fragment_transaction.xml +++ b/app/src/main/res/layout/fragment_transaction.xml @@ -290,12 +290,38 @@ android:layout_height="wrap_content" android:text="@string/deposit_hash" /> - + android:gravity="center_vertical" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" + android:layout_marginTop="5dp" + android:layout_marginBottom="12dp" + android:orientation="horizontal"> + + + + + - \ No newline at end of file + diff --git a/app/src/test/java/one/mixin/android/ui/home/web3/trade/TradeInputTest.kt b/app/src/test/java/one/mixin/android/ui/home/web3/trade/TradeInputTest.kt new file mode 100644 index 0000000000..8a29dd5092 --- /dev/null +++ b/app/src/test/java/one/mixin/android/ui/home/web3/trade/TradeInputTest.kt @@ -0,0 +1,51 @@ +package one.mixin.android.ui.home.web3.trade + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class TradeInputTest { + @Test + fun tradeAmountInputUsesWalletSpecificDecimalPlaces() { + assertEquals(8, tradeInputMaxDecimalPlaces(isCommonWallet = false, precision = 18)) + assertEquals(6, tradeInputMaxDecimalPlaces(isCommonWallet = true, precision = 6)) + assertEquals(0, tradeInputMaxDecimalPlaces(isCommonWallet = true, precision = 0)) + assertEquals(8, tradeInputMaxDecimalPlaces(isCommonWallet = true, precision = -1)) + } + + @Test + fun tradePriceInputAllowsAtMostEightDecimalPlaces() { + assertEquals(8, tradePriceInputMaxDecimalPlaces()) + assertTrue(isTradeInputDecimalAllowed("1.12345678", tradePriceInputMaxDecimalPlaces())) + assertFalse(isTradeInputDecimalAllowed("1.123456789", tradePriceInputMaxDecimalPlaces())) + } + + @Test + fun tradeAmountInputAllowsAtMostEightDecimalPlaces() { + assertTrue(isTradeInputDecimalAllowed("")) + assertTrue(isTradeInputDecimalAllowed("12")) + assertTrue(isTradeInputDecimalAllowed("12.")) + assertTrue(isTradeInputDecimalAllowed("12.12345678")) + assertTrue(isTradeInputDecimalAllowed("0.00000000")) + + assertFalse(isTradeInputDecimalAllowed("12.123456789")) + assertFalse(isTradeInputDecimalAllowed("0.000000001")) + + assertTrue(isTradeInputDecimalAllowed("12.123456789", maxDecimalPlaces = null)) + } + + @Test + fun tradeAmountInputLimitsProgrammaticValuesToEightDecimalPlaces() { + assertEquals("", limitTradeInputDecimalPlaces("")) + assertEquals("12", limitTradeInputDecimalPlaces("12")) + assertEquals("12.", limitTradeInputDecimalPlaces("12.")) + assertEquals("12.12345678", limitTradeInputDecimalPlaces("12.12345678")) + assertEquals("12.12345678", limitTradeInputDecimalPlaces("12.123456789")) + assertEquals("0.00000000", limitTradeInputDecimalPlaces("0.000000001")) + assertEquals( + "12.123456789", + limitTradeInputDecimalPlaces("12.123456789", maxDecimalPlaces = null) + ) + } +} diff --git a/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt b/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt new file mode 100644 index 0000000000..8ca98b5f33 --- /dev/null +++ b/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt @@ -0,0 +1,78 @@ +package one.mixin.android.vo + +import kotlin.test.Test +import kotlin.test.assertEquals +import one.mixin.android.vo.safe.RawTransaction +import one.mixin.android.vo.safe.RawTransactionState +import one.mixin.android.vo.safe.RawTransactionType +import one.mixin.android.vo.safe.SafeSnapshotType + +class SnapshotItemTest { + @Test + fun `pending hash is shown when raw transaction is unspent`() { + val snapshot = snapshotItem(type = SafeSnapshotType.snapshot.name, traceId = "trace-id") + val rawTransaction = rawTransaction("trace-id", RawTransactionState.signed) + + assertEquals(true, snapshot.shouldShowPendingHash(rawTransaction)) + } + + @Test + fun `pending hash is hidden when raw transaction was sent`() { + val snapshot = snapshotItem(type = SafeSnapshotType.snapshot.name, traceId = "trace-id") + val rawTransaction = rawTransaction("trace-id", RawTransactionState.spent) + + assertEquals(false, snapshot.shouldShowPendingHash(rawTransaction)) + } + + @Test + fun `pending hash is hidden when raw transaction is missing`() { + val snapshot = snapshotItem(type = SafeSnapshotType.withdrawal.name, traceId = "trace-id") + + assertEquals(false, snapshot.shouldShowPendingHash(null)) + } + + private fun rawTransaction( + requestId: String, + state: RawTransactionState, + ) = RawTransaction( + requestId = requestId, + rawTransaction = "raw", + receiverId = "", + type = RawTransactionType.TRANSFER, + state = state, + createdAt = "2026-06-03T00:00:00Z", + inscriptionHash = null, + ) + + private fun snapshotItem( + type: String, + traceId: String?, + ) = SnapshotItem( + snapshotId = "snapshot-id", + type = type, + assetId = "asset-id", + amount = "-1", + createdAt = "2026-06-03T00:00:00Z", + opponentId = "opponent-id", + opponentFullName = null, + transactionHash = "hash", + memo = null, + assetSymbol = "XIN", + confirmations = null, + avatarUrl = null, + assetConfirmations = 0, + traceId = traceId, + openingBalance = null, + closingBalance = null, + deposit = null, + withdrawal = null, + label = null, + inscriptionHash = null, + collectionHash = null, + name = null, + sequence = null, + contentType = null, + contentUrl = null, + iconUrl = null, + ) +}