Handle even more instance defaults (#2612)
* handle media size instance limits * remove unused attributes from Instance entity * support max_media_attachments * support pleroma field limits, remove max_bio_chars support * improve field input margin * fix tests * MAX_ACCOUNT_FIELDS -> DEFAULT_MAX_ACCOUNT_FIELDS * improve "add field" button behavior * fix copy paste mistake in AccountFieldEditAdapter * refactor sendStatus to be a suspending function
This commit is contained in:
parent
25f637f0a8
commit
1b6a0908f6
16 changed files with 1219 additions and 308 deletions
929
app/schemas/com.keylesspalace.tusky.db.AppDatabase/40.json
Normal file
929
app/schemas/com.keylesspalace.tusky.db.AppDatabase/40.json
Normal file
|
@ -0,0 +1,929 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 40,
|
||||||
|
"identityHash": "0423fb3f7d09db5f12023f2f4e7297b5",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "DraftEntity",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountId` INTEGER NOT NULL, `inReplyToId` TEXT, `content` TEXT, `contentWarning` TEXT, `sensitive` INTEGER NOT NULL, `visibility` INTEGER NOT NULL, `attachments` TEXT NOT NULL, `poll` TEXT, `failedToSend` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "accountId",
|
||||||
|
"columnName": "accountId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "inReplyToId",
|
||||||
|
"columnName": "inReplyToId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "content",
|
||||||
|
"columnName": "content",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentWarning",
|
||||||
|
"columnName": "contentWarning",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sensitive",
|
||||||
|
"columnName": "sensitive",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "visibility",
|
||||||
|
"columnName": "visibility",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "attachments",
|
||||||
|
"columnName": "attachments",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "poll",
|
||||||
|
"columnName": "poll",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "failedToSend",
|
||||||
|
"columnName": "failedToSend",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "AccountEntity",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `domain` TEXT NOT NULL, `accessToken` TEXT NOT NULL, `clientId` TEXT, `clientSecret` TEXT, `isActive` INTEGER NOT NULL, `accountId` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `profilePictureUrl` TEXT NOT NULL, `notificationsEnabled` INTEGER NOT NULL, `notificationsMentioned` INTEGER NOT NULL, `notificationsFollowed` INTEGER NOT NULL, `notificationsFollowRequested` INTEGER NOT NULL, `notificationsReblogged` INTEGER NOT NULL, `notificationsFavorited` INTEGER NOT NULL, `notificationsPolls` INTEGER NOT NULL, `notificationsSubscriptions` INTEGER NOT NULL, `notificationsSignUps` INTEGER NOT NULL, `notificationsUpdates` INTEGER NOT NULL, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `activeNotifications` TEXT NOT NULL, `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL, `oauthScopes` TEXT NOT NULL, `unifiedPushUrl` TEXT NOT NULL, `pushPubKey` TEXT NOT NULL, `pushPrivKey` TEXT NOT NULL, `pushAuth` TEXT NOT NULL, `pushServerKey` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "domain",
|
||||||
|
"columnName": "domain",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "accessToken",
|
||||||
|
"columnName": "accessToken",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "clientId",
|
||||||
|
"columnName": "clientId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "clientSecret",
|
||||||
|
"columnName": "clientSecret",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isActive",
|
||||||
|
"columnName": "isActive",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "accountId",
|
||||||
|
"columnName": "accountId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "displayName",
|
||||||
|
"columnName": "displayName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "profilePictureUrl",
|
||||||
|
"columnName": "profilePictureUrl",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsEnabled",
|
||||||
|
"columnName": "notificationsEnabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsMentioned",
|
||||||
|
"columnName": "notificationsMentioned",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsFollowed",
|
||||||
|
"columnName": "notificationsFollowed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsFollowRequested",
|
||||||
|
"columnName": "notificationsFollowRequested",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsReblogged",
|
||||||
|
"columnName": "notificationsReblogged",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsFavorited",
|
||||||
|
"columnName": "notificationsFavorited",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsPolls",
|
||||||
|
"columnName": "notificationsPolls",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsSubscriptions",
|
||||||
|
"columnName": "notificationsSubscriptions",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsSignUps",
|
||||||
|
"columnName": "notificationsSignUps",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsUpdates",
|
||||||
|
"columnName": "notificationsUpdates",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationSound",
|
||||||
|
"columnName": "notificationSound",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationVibration",
|
||||||
|
"columnName": "notificationVibration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationLight",
|
||||||
|
"columnName": "notificationLight",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "defaultPostPrivacy",
|
||||||
|
"columnName": "defaultPostPrivacy",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "defaultMediaSensitivity",
|
||||||
|
"columnName": "defaultMediaSensitivity",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alwaysShowSensitiveMedia",
|
||||||
|
"columnName": "alwaysShowSensitiveMedia",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alwaysOpenSpoiler",
|
||||||
|
"columnName": "alwaysOpenSpoiler",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mediaPreviewEnabled",
|
||||||
|
"columnName": "mediaPreviewEnabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastNotificationId",
|
||||||
|
"columnName": "lastNotificationId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "activeNotifications",
|
||||||
|
"columnName": "activeNotifications",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "emojis",
|
||||||
|
"columnName": "emojis",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tabPreferences",
|
||||||
|
"columnName": "tabPreferences",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationsFilter",
|
||||||
|
"columnName": "notificationsFilter",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "oauthScopes",
|
||||||
|
"columnName": "oauthScopes",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "unifiedPushUrl",
|
||||||
|
"columnName": "unifiedPushUrl",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pushPubKey",
|
||||||
|
"columnName": "pushPubKey",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pushPrivKey",
|
||||||
|
"columnName": "pushPrivKey",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pushAuth",
|
||||||
|
"columnName": "pushAuth",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pushServerKey",
|
||||||
|
"columnName": "pushServerKey",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_AccountEntity_domain_accountId",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"domain",
|
||||||
|
"accountId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_AccountEntity_domain_accountId` ON `${TABLE_NAME}` (`domain`, `accountId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "InstanceEntity",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`instance` TEXT NOT NULL, `emojiList` TEXT, `maximumTootCharacters` INTEGER, `maxPollOptions` INTEGER, `maxPollOptionLength` INTEGER, `minPollDuration` INTEGER, `maxPollDuration` INTEGER, `charactersReservedPerUrl` INTEGER, `version` TEXT, `videoSizeLimit` INTEGER, `imageSizeLimit` INTEGER, `imageMatrixLimit` INTEGER, `maxMediaAttachments` INTEGER, `maxFields` INTEGER, `maxFieldNameLength` INTEGER, `maxFieldValueLength` INTEGER, PRIMARY KEY(`instance`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "instance",
|
||||||
|
"columnName": "instance",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "emojiList",
|
||||||
|
"columnName": "emojiList",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "maximumTootCharacters",
|
||||||
|
"columnName": "maximumTootCharacters",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "maxPollOptions",
|
||||||
|
"columnName": "maxPollOptions",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "maxPollOptionLength",
|
||||||
|
"columnName": "maxPollOptionLength",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minPollDuration",
|
||||||
|
"columnName": "minPollDuration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "maxPollDuration",
|
||||||
|
"columnName": "maxPollDuration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charactersReservedPerUrl",
|
||||||
|
"columnName": "charactersReservedPerUrl",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "version",
|
||||||
|
"columnName": "version",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "videoSizeLimit",
|
||||||
|
"columnName": "videoSizeLimit",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "imageSizeLimit",
|
||||||
|
"columnName": "imageSizeLimit",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "imageMatrixLimit",
|
||||||
|
"columnName": "imageMatrixLimit",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "maxMediaAttachments",
|
||||||
|
"columnName": "maxMediaAttachments",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "maxFields",
|
||||||
|
"columnName": "maxFields",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "maxFieldNameLength",
|
||||||
|
"columnName": "maxFieldNameLength",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "maxFieldValueLength",
|
||||||
|
"columnName": "maxFieldValueLength",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"instance"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "TimelineStatusEntity",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `repliesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT NOT NULL, `visibility` INTEGER NOT NULL, `attachments` TEXT, `mentions` TEXT, `tags` TEXT, `application` TEXT, `reblogServerId` TEXT, `reblogAccountId` TEXT, `poll` TEXT, `muted` INTEGER, `expanded` INTEGER NOT NULL, `contentCollapsed` INTEGER NOT NULL, `contentShowing` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `card` TEXT, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "serverId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timelineUserId",
|
||||||
|
"columnName": "timelineUserId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "authorServerId",
|
||||||
|
"columnName": "authorServerId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "inReplyToId",
|
||||||
|
"columnName": "inReplyToId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "inReplyToAccountId",
|
||||||
|
"columnName": "inReplyToAccountId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "content",
|
||||||
|
"columnName": "content",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "createdAt",
|
||||||
|
"columnName": "createdAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "emojis",
|
||||||
|
"columnName": "emojis",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reblogsCount",
|
||||||
|
"columnName": "reblogsCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "favouritesCount",
|
||||||
|
"columnName": "favouritesCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "repliesCount",
|
||||||
|
"columnName": "repliesCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reblogged",
|
||||||
|
"columnName": "reblogged",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarked",
|
||||||
|
"columnName": "bookmarked",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "favourited",
|
||||||
|
"columnName": "favourited",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sensitive",
|
||||||
|
"columnName": "sensitive",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "spoilerText",
|
||||||
|
"columnName": "spoilerText",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "visibility",
|
||||||
|
"columnName": "visibility",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "attachments",
|
||||||
|
"columnName": "attachments",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mentions",
|
||||||
|
"columnName": "mentions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tags",
|
||||||
|
"columnName": "tags",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "application",
|
||||||
|
"columnName": "application",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reblogServerId",
|
||||||
|
"columnName": "reblogServerId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reblogAccountId",
|
||||||
|
"columnName": "reblogAccountId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "poll",
|
||||||
|
"columnName": "poll",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "muted",
|
||||||
|
"columnName": "muted",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "expanded",
|
||||||
|
"columnName": "expanded",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentCollapsed",
|
||||||
|
"columnName": "contentCollapsed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentShowing",
|
||||||
|
"columnName": "contentShowing",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pinned",
|
||||||
|
"columnName": "pinned",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "card",
|
||||||
|
"columnName": "card",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"serverId",
|
||||||
|
"timelineUserId"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_TimelineStatusEntity_authorServerId_timelineUserId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"authorServerId",
|
||||||
|
"timelineUserId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_TimelineStatusEntity_authorServerId_timelineUserId` ON `${TABLE_NAME}` (`authorServerId`, `timelineUserId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "TimelineAccountEntity",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"authorServerId",
|
||||||
|
"timelineUserId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"serverId",
|
||||||
|
"timelineUserId"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "TimelineAccountEntity",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `timelineUserId` INTEGER NOT NULL, `localUsername` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `url` TEXT NOT NULL, `avatar` TEXT NOT NULL, `emojis` TEXT NOT NULL, `bot` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `timelineUserId`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "serverId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timelineUserId",
|
||||||
|
"columnName": "timelineUserId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "localUsername",
|
||||||
|
"columnName": "localUsername",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "displayName",
|
||||||
|
"columnName": "displayName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "avatar",
|
||||||
|
"columnName": "avatar",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "emojis",
|
||||||
|
"columnName": "emojis",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bot",
|
||||||
|
"columnName": "bot",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"serverId",
|
||||||
|
"timelineUserId"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "ConversationEntity",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `id` TEXT NOT NULL, `order` INTEGER NOT NULL, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_repliesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_tags` TEXT, `s_showingHiddenContent` INTEGER NOT NULL, `s_expanded` INTEGER NOT NULL, `s_collapsed` INTEGER NOT NULL, `s_muted` INTEGER NOT NULL, `s_poll` TEXT, PRIMARY KEY(`id`, `accountId`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "accountId",
|
||||||
|
"columnName": "accountId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "order",
|
||||||
|
"columnName": "order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "accounts",
|
||||||
|
"columnName": "accounts",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "unread",
|
||||||
|
"columnName": "unread",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.id",
|
||||||
|
"columnName": "s_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.url",
|
||||||
|
"columnName": "s_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.inReplyToId",
|
||||||
|
"columnName": "s_inReplyToId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.inReplyToAccountId",
|
||||||
|
"columnName": "s_inReplyToAccountId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.account",
|
||||||
|
"columnName": "s_account",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.content",
|
||||||
|
"columnName": "s_content",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.createdAt",
|
||||||
|
"columnName": "s_createdAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.emojis",
|
||||||
|
"columnName": "s_emojis",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.favouritesCount",
|
||||||
|
"columnName": "s_favouritesCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.repliesCount",
|
||||||
|
"columnName": "s_repliesCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.favourited",
|
||||||
|
"columnName": "s_favourited",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.bookmarked",
|
||||||
|
"columnName": "s_bookmarked",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.sensitive",
|
||||||
|
"columnName": "s_sensitive",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.spoilerText",
|
||||||
|
"columnName": "s_spoilerText",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.attachments",
|
||||||
|
"columnName": "s_attachments",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.mentions",
|
||||||
|
"columnName": "s_mentions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.tags",
|
||||||
|
"columnName": "s_tags",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.showingHiddenContent",
|
||||||
|
"columnName": "s_showingHiddenContent",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.expanded",
|
||||||
|
"columnName": "s_expanded",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.collapsed",
|
||||||
|
"columnName": "s_collapsed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.muted",
|
||||||
|
"columnName": "s_muted",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastStatus.poll",
|
||||||
|
"columnName": "s_poll",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id",
|
||||||
|
"accountId"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"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, '0423fb3f7d09db5f12023f2f4e7297b5')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import android.widget.ImageView
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
@ -37,6 +38,7 @@ import com.canhub.cropper.CropImageContract
|
||||||
import com.canhub.cropper.options
|
import com.canhub.cropper.options
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
|
import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
|
||||||
|
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
|
||||||
import com.keylesspalace.tusky.databinding.ActivityEditProfileBinding
|
import com.keylesspalace.tusky.databinding.ActivityEditProfileBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
|
@ -50,6 +52,7 @@ import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
import com.mikepenz.iconics.utils.sizeDp
|
import com.mikepenz.iconics.utils.sizeDp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class EditProfileActivity : BaseActivity(), Injectable {
|
class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
|
@ -58,8 +61,6 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
const val AVATAR_SIZE = 400
|
const val AVATAR_SIZE = 400
|
||||||
const val HEADER_WIDTH = 1500
|
const val HEADER_WIDTH = 1500
|
||||||
const val HEADER_HEIGHT = 500
|
const val HEADER_HEIGHT = 500
|
||||||
|
|
||||||
private const val MAX_ACCOUNT_FIELDS = 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -71,6 +72,8 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
|
|
||||||
private val accountFieldEditAdapter = AccountFieldEditAdapter()
|
private val accountFieldEditAdapter = AccountFieldEditAdapter()
|
||||||
|
|
||||||
|
private var maxAccountFields = InstanceInfoRepository.DEFAULT_MAX_ACCOUNT_FIELDS
|
||||||
|
|
||||||
private enum class PickType {
|
private enum class PickType {
|
||||||
AVATAR,
|
AVATAR,
|
||||||
HEADER
|
HEADER
|
||||||
|
@ -112,7 +115,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
|
|
||||||
binding.addFieldButton.setOnClickListener {
|
binding.addFieldButton.setOnClickListener {
|
||||||
accountFieldEditAdapter.addField()
|
accountFieldEditAdapter.addField()
|
||||||
if (accountFieldEditAdapter.itemCount >= MAX_ACCOUNT_FIELDS) {
|
if (accountFieldEditAdapter.itemCount >= maxAccountFields) {
|
||||||
it.isVisible = false
|
it.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +137,8 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
binding.lockedCheckBox.isChecked = me.locked
|
binding.lockedCheckBox.isChecked = me.locked
|
||||||
|
|
||||||
accountFieldEditAdapter.setFields(me.source?.fields ?: emptyList())
|
accountFieldEditAdapter.setFields(me.source?.fields ?: emptyList())
|
||||||
binding.addFieldButton.isEnabled = me.source?.fields?.size ?: 0 < MAX_ACCOUNT_FIELDS
|
binding.addFieldButton.isVisible =
|
||||||
|
(me.source?.fields?.size ?: 0) < maxAccountFields
|
||||||
|
|
||||||
if (viewModel.avatarData.value == null) {
|
if (viewModel.avatarData.value == null) {
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
|
@ -165,13 +169,12 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.obtainInstance()
|
lifecycleScope.launch {
|
||||||
viewModel.instanceData.observe(this) { result ->
|
viewModel.instanceData.collect { instanceInfo ->
|
||||||
if (result is Success) {
|
maxAccountFields = instanceInfo.maxFields
|
||||||
val instance = result.data
|
accountFieldEditAdapter.setFieldLimits(instanceInfo.maxFieldNameLength, instanceInfo.maxFieldValueLength)
|
||||||
if (instance?.maxBioChars != null && instance.maxBioChars > 0) {
|
binding.addFieldButton.isVisible =
|
||||||
binding.noteEditTextLayout.counterMaxLength = instance.maxBioChars
|
accountFieldEditAdapter.itemCount < maxAccountFields
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ import com.keylesspalace.tusky.util.BindingHolder
|
||||||
class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditFieldBinding>>() {
|
class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditFieldBinding>>() {
|
||||||
|
|
||||||
private val fieldData = mutableListOf<MutableStringPair>()
|
private val fieldData = mutableListOf<MutableStringPair>()
|
||||||
|
private var maxNameLength: Int? = null
|
||||||
|
private var maxValueLength: Int? = null
|
||||||
|
|
||||||
fun setFields(fields: List<StringField>) {
|
fun setFields(fields: List<StringField>) {
|
||||||
fieldData.clear()
|
fieldData.clear()
|
||||||
|
@ -41,6 +43,12 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setFieldLimits(maxNameLength: Int?, maxValueLength: Int?) {
|
||||||
|
this.maxNameLength = maxNameLength
|
||||||
|
this.maxValueLength = maxValueLength
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
fun getFieldData(): List<StringField> {
|
fun getFieldData(): List<StringField> {
|
||||||
return fieldData.map {
|
return fieldData.map {
|
||||||
StringField(it.first, it.second)
|
StringField(it.first, it.second)
|
||||||
|
@ -60,10 +68,20 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: BindingHolder<ItemEditFieldBinding>, position: Int) {
|
override fun onBindViewHolder(holder: BindingHolder<ItemEditFieldBinding>, position: Int) {
|
||||||
holder.binding.accountFieldName.setText(fieldData[position].first)
|
holder.binding.accountFieldNameText.setText(fieldData[position].first)
|
||||||
holder.binding.accountFieldValue.setText(fieldData[position].second)
|
holder.binding.accountFieldValueText.setText(fieldData[position].second)
|
||||||
|
|
||||||
holder.binding.accountFieldName.addTextChangedListener(object : TextWatcher {
|
holder.binding.accountFieldNameTextLayout.isCounterEnabled = maxNameLength != null
|
||||||
|
maxNameLength?.let {
|
||||||
|
holder.binding.accountFieldNameTextLayout.counterMaxLength = it
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.binding.accountFieldValueTextLayout.isCounterEnabled = maxValueLength != null
|
||||||
|
maxValueLength?.let {
|
||||||
|
holder.binding.accountFieldValueTextLayout.counterMaxLength = it
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.binding.accountFieldNameText.addTextChangedListener(object : TextWatcher {
|
||||||
override fun afterTextChanged(newText: Editable) {
|
override fun afterTextChanged(newText: Editable) {
|
||||||
fieldData[holder.bindingAdapterPosition].first = newText.toString()
|
fieldData[holder.bindingAdapterPosition].first = newText.toString()
|
||||||
}
|
}
|
||||||
|
@ -73,7 +91,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
holder.binding.accountFieldValue.addTextChangedListener(object : TextWatcher {
|
holder.binding.accountFieldValueText.addTextChangedListener(object : TextWatcher {
|
||||||
override fun afterTextChanged(newText: Editable) {
|
override fun afterTextChanged(newText: Editable) {
|
||||||
fieldData[holder.bindingAdapterPosition].second = newText.toString()
|
fieldData[holder.bindingAdapterPosition].second = newText.toString()
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,6 @@ import androidx.core.view.ContentInfoCompat
|
||||||
import androidx.core.view.OnReceiveContentListener
|
import androidx.core.view.OnReceiveContentListener
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.asLiveData
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -85,8 +84,6 @@ import com.keylesspalace.tusky.settings.PrefKeys
|
||||||
import com.keylesspalace.tusky.util.PickMediaFiles
|
import com.keylesspalace.tusky.util.PickMediaFiles
|
||||||
import com.keylesspalace.tusky.util.ThemeUtils
|
import com.keylesspalace.tusky.util.ThemeUtils
|
||||||
import com.keylesspalace.tusky.util.afterTextChanged
|
import com.keylesspalace.tusky.util.afterTextChanged
|
||||||
import com.keylesspalace.tusky.util.combineLiveData
|
|
||||||
import com.keylesspalace.tusky.util.combineOptionalLiveData
|
|
||||||
import com.keylesspalace.tusky.util.getMediaSize
|
import com.keylesspalace.tusky.util.getMediaSize
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.highlightSpans
|
import com.keylesspalace.tusky.util.highlightSpans
|
||||||
|
@ -95,11 +92,13 @@ import com.keylesspalace.tusky.util.onTextChanged
|
||||||
import com.keylesspalace.tusky.util.show
|
import com.keylesspalace.tusky.util.show
|
||||||
import com.keylesspalace.tusky.util.viewBinding
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
import com.keylesspalace.tusky.util.visible
|
import com.keylesspalace.tusky.util.visible
|
||||||
import com.keylesspalace.tusky.util.withLifecycleContext
|
|
||||||
import com.mikepenz.iconics.IconicsDrawable
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
import com.mikepenz.iconics.utils.sizeDp
|
import com.mikepenz.iconics.utils.sizeDp
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -138,8 +137,7 @@ class ComposeActivity :
|
||||||
|
|
||||||
private val binding by viewBinding(ActivityComposeBinding::inflate)
|
private val binding by viewBinding(ActivityComposeBinding::inflate)
|
||||||
|
|
||||||
private val maxUploadMediaNumber = 4
|
private var maxUploadMediaNumber = InstanceInfoRepository.DEFAULT_MAX_MEDIA_ATTACHMENTS
|
||||||
private var mediaCount = 0
|
|
||||||
|
|
||||||
private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
|
private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -147,7 +145,7 @@ class ComposeActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val pickMediaFile = registerForActivityResult(PickMediaFiles()) { uris ->
|
private val pickMediaFile = registerForActivityResult(PickMediaFiles()) { uris ->
|
||||||
if (mediaCount + uris.size > maxUploadMediaNumber) {
|
if (viewModel.media.value.size + uris.size > maxUploadMediaNumber) {
|
||||||
Toast.makeText(this, resources.getQuantityString(R.plurals.error_upload_max_media_reached, maxUploadMediaNumber, maxUploadMediaNumber), Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, resources.getQuantityString(R.plurals.error_upload_max_media_reached, maxUploadMediaNumber, maxUploadMediaNumber), Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
uris.forEach { uri ->
|
uris.forEach { uri ->
|
||||||
|
@ -224,8 +222,8 @@ class ComposeActivity :
|
||||||
binding.composeMediaPreviewBar.adapter = mediaAdapter
|
binding.composeMediaPreviewBar.adapter = mediaAdapter
|
||||||
binding.composeMediaPreviewBar.itemAnimator = null
|
binding.composeMediaPreviewBar.itemAnimator = null
|
||||||
|
|
||||||
subscribeToUpdates(mediaAdapter)
|
|
||||||
setupButtons()
|
setupButtons()
|
||||||
|
subscribeToUpdates(mediaAdapter)
|
||||||
|
|
||||||
photoUploadUri = savedInstanceState?.getParcelable(PHOTO_UPLOAD_URI_KEY)
|
photoUploadUri = savedInstanceState?.getParcelable(PHOTO_UPLOAD_URI_KEY)
|
||||||
|
|
||||||
|
@ -363,36 +361,48 @@ class ComposeActivity :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun subscribeToUpdates(mediaAdapter: MediaPreviewAdapter) {
|
private fun subscribeToUpdates(mediaAdapter: MediaPreviewAdapter) {
|
||||||
withLifecycleContext {
|
lifecycleScope.launch {
|
||||||
viewModel.instanceInfo.observe { instanceData ->
|
viewModel.instanceInfo.collect { instanceData ->
|
||||||
maximumTootCharacters = instanceData.maxChars
|
maximumTootCharacters = instanceData.maxChars
|
||||||
charactersReservedPerUrl = instanceData.charactersReservedPerUrl
|
charactersReservedPerUrl = instanceData.charactersReservedPerUrl
|
||||||
|
maxUploadMediaNumber = instanceData.maxMediaAttachments
|
||||||
updateVisibleCharactersLeft()
|
updateVisibleCharactersLeft()
|
||||||
}
|
}
|
||||||
viewModel.emoji.observe { emoji -> setEmojiList(emoji) }
|
}
|
||||||
combineLiveData(viewModel.markMediaAsSensitive, viewModel.showContentWarning) { markSensitive, showContentWarning ->
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.emoji.collect(::setEmojiList)
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.showContentWarning.combine(viewModel.markMediaAsSensitive) { showContentWarning, markSensitive ->
|
||||||
updateSensitiveMediaToggle(markSensitive, showContentWarning)
|
updateSensitiveMediaToggle(markSensitive, showContentWarning)
|
||||||
showContentWarning(showContentWarning)
|
showContentWarning(showContentWarning)
|
||||||
}.subscribe()
|
}.collect()
|
||||||
viewModel.statusVisibility.observe { visibility ->
|
}
|
||||||
setStatusVisibility(visibility)
|
|
||||||
}
|
|
||||||
lifecycleScope.launch {
|
|
||||||
viewModel.media.collect { media ->
|
|
||||||
mediaAdapter.submitList(media)
|
|
||||||
if (media.size != mediaCount) {
|
|
||||||
mediaCount = media.size
|
|
||||||
binding.composeMediaPreviewBar.visible(media.isNotEmpty())
|
|
||||||
updateSensitiveMediaToggle(viewModel.markMediaAsSensitive.value != false, viewModel.showContentWarning.value != false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.poll.observe { poll ->
|
lifecycleScope.launch {
|
||||||
|
viewModel.statusVisibility.collect(::setStatusVisibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.media.collect { media ->
|
||||||
|
mediaAdapter.submitList(media)
|
||||||
|
|
||||||
|
binding.composeMediaPreviewBar.visible(media.isNotEmpty())
|
||||||
|
updateSensitiveMediaToggle(viewModel.markMediaAsSensitive.value, viewModel.showContentWarning.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.poll.collect { poll ->
|
||||||
binding.pollPreview.visible(poll != null)
|
binding.pollPreview.visible(poll != null)
|
||||||
poll?.let(binding.pollPreview::setPoll)
|
poll?.let(binding.pollPreview::setPoll)
|
||||||
}
|
}
|
||||||
viewModel.scheduledAt.observe { scheduledAt ->
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.scheduledAt.collect { scheduledAt ->
|
||||||
if (scheduledAt == null) {
|
if (scheduledAt == null) {
|
||||||
binding.composeScheduleView.resetSchedule()
|
binding.composeScheduleView.resetSchedule()
|
||||||
} else {
|
} else {
|
||||||
|
@ -400,22 +410,30 @@ class ComposeActivity :
|
||||||
}
|
}
|
||||||
updateScheduleButton()
|
updateScheduleButton()
|
||||||
}
|
}
|
||||||
combineOptionalLiveData(viewModel.media.asLiveData(), viewModel.poll) { media, poll ->
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.media.combine(viewModel.poll) { media, poll ->
|
||||||
val active = poll == null &&
|
val active = poll == null &&
|
||||||
media!!.size != 4 &&
|
media.size < maxUploadMediaNumber &&
|
||||||
(media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
|
(media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
|
||||||
enableButton(binding.composeAddMediaButton, active, active)
|
enableButton(binding.composeAddMediaButton, active, active)
|
||||||
enablePollButton(media.isNullOrEmpty())
|
enablePollButton(media.isEmpty())
|
||||||
}.subscribe()
|
}.collect()
|
||||||
viewModel.uploadError.observe { throwable ->
|
}
|
||||||
Log.w(TAG, "media upload failed", throwable)
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.uploadError.collect { throwable ->
|
||||||
if (throwable is UploadServerError) {
|
if (throwable is UploadServerError) {
|
||||||
displayTransientError(throwable.errorMessage)
|
displayTransientError(throwable.errorMessage)
|
||||||
} else {
|
} else {
|
||||||
displayTransientError(R.string.error_media_upload_sending)
|
displayTransientError(R.string.error_media_upload_sending)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModel.setupComplete.observe {
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.setupComplete.collect {
|
||||||
// Focus may have changed during view model setup, ensure initial focus is on the edit field
|
// Focus may have changed during view model setup, ensure initial focus is on the edit field
|
||||||
binding.composeEditField.requestFocus()
|
binding.composeEditField.requestFocus()
|
||||||
}
|
}
|
||||||
|
@ -711,13 +729,17 @@ class ComposeActivity :
|
||||||
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openPollDialog() {
|
private fun openPollDialog() = lifecycleScope.launch {
|
||||||
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
val instanceParams = viewModel.instanceInfo.value!!
|
val instanceParams = viewModel.instanceInfo.first()
|
||||||
showAddPollDialog(
|
showAddPollDialog(
|
||||||
this, viewModel.poll.value, instanceParams.pollMaxOptions,
|
context = this@ComposeActivity,
|
||||||
instanceParams.pollMaxLength, instanceParams.pollMinDuration, instanceParams.pollMaxDuration,
|
poll = viewModel.poll.value,
|
||||||
viewModel::updatePoll
|
maxOptionCount = instanceParams.pollMaxOptions,
|
||||||
|
maxOptionLength = instanceParams.pollMaxLength,
|
||||||
|
minDuration = instanceParams.pollMinDuration,
|
||||||
|
maxDuration = instanceParams.pollMaxDuration,
|
||||||
|
onUpdatePoll = viewModel::updatePoll
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -768,7 +790,7 @@ class ComposeActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var length = binding.composeEditField.length() - offset
|
var length = binding.composeEditField.length() - offset
|
||||||
if (viewModel.showContentWarning.value!!) {
|
if (viewModel.showContentWarning.value) {
|
||||||
length += binding.composeContentWarningField.length()
|
length += binding.composeContentWarningField.length()
|
||||||
}
|
}
|
||||||
return length
|
return length
|
||||||
|
@ -822,7 +844,7 @@ class ComposeActivity :
|
||||||
enableButtons(false)
|
enableButtons(false)
|
||||||
val contentText = binding.composeEditField.text.toString()
|
val contentText = binding.composeEditField.text.toString()
|
||||||
var spoilerText = ""
|
var spoilerText = ""
|
||||||
if (viewModel.showContentWarning.value!!) {
|
if (viewModel.showContentWarning.value) {
|
||||||
spoilerText = binding.composeContentWarningField.text.toString()
|
spoilerText = binding.composeContentWarningField.text.toString()
|
||||||
}
|
}
|
||||||
val characterCount = calculateTextLength()
|
val characterCount = calculateTextLength()
|
||||||
|
@ -837,9 +859,8 @@ class ComposeActivity :
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.sendStatus(contentText, spoilerText).observe(
|
lifecycleScope.launch {
|
||||||
this
|
viewModel.sendStatus(contentText, spoilerText)
|
||||||
) {
|
|
||||||
finishingUploadDialog?.dismiss()
|
finishingUploadDialog?.dismiss()
|
||||||
deleteDraftAndFinish()
|
deleteDraftAndFinish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,7 @@ package com.keylesspalace.tusky.components.compose
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.asLiveData
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import at.connyduck.calladapter.networkresult.fold
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
|
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
|
||||||
|
@ -38,30 +35,34 @@ import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.service.ServiceClient
|
import com.keylesspalace.tusky.service.ServiceClient
|
||||||
import com.keylesspalace.tusky.service.StatusToSend
|
import com.keylesspalace.tusky.service.StatusToSend
|
||||||
import com.keylesspalace.tusky.util.combineLiveData
|
|
||||||
import com.keylesspalace.tusky.util.randomAlphanumericString
|
import com.keylesspalace.tusky.util.randomAlphanumericString
|
||||||
import com.keylesspalace.tusky.util.toLiveData
|
|
||||||
import io.reactivex.rxjava3.core.Observable
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.flow.updateAndGet
|
import kotlinx.coroutines.flow.updateAndGet
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.rx3.rxSingle
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
class ComposeViewModel @Inject constructor(
|
class ComposeViewModel @Inject constructor(
|
||||||
private val api: MastodonApi,
|
private val api: MastodonApi,
|
||||||
private val accountManager: AccountManager,
|
private val accountManager: AccountManager,
|
||||||
private val mediaUploader: MediaUploader,
|
private val mediaUploader: MediaUploader,
|
||||||
private val serviceClient: ServiceClient,
|
private val serviceClient: ServiceClient,
|
||||||
private val draftHelper: DraftHelper,
|
private val draftHelper: DraftHelper,
|
||||||
private val instanceInfoRepo: InstanceInfoRepository
|
instanceInfoRepo: InstanceInfoRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private var replyingStatusAuthor: String? = null
|
private var replyingStatusAuthor: String? = null
|
||||||
|
@ -76,40 +77,32 @@ class ComposeViewModel @Inject constructor(
|
||||||
private var contentWarningStateChanged: Boolean = false
|
private var contentWarningStateChanged: Boolean = false
|
||||||
private var modifiedInitialState: Boolean = false
|
private var modifiedInitialState: Boolean = false
|
||||||
|
|
||||||
val instanceInfo: MutableLiveData<InstanceInfo> = MutableLiveData()
|
val instanceInfo: SharedFlow<InstanceInfo> = instanceInfoRepo::getInstanceInfo.asFlow()
|
||||||
|
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||||
|
|
||||||
val emoji: MutableLiveData<List<Emoji>?> = MutableLiveData()
|
val emoji: SharedFlow<List<Emoji>> = instanceInfoRepo::getEmojis.asFlow()
|
||||||
val markMediaAsSensitive =
|
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||||
mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
|
|
||||||
|
|
||||||
val statusVisibility = mutableLiveData(Status.Visibility.UNKNOWN)
|
val markMediaAsSensitive: MutableStateFlow<Boolean> =
|
||||||
val showContentWarning = mutableLiveData(false)
|
MutableStateFlow(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
|
||||||
val setupComplete = mutableLiveData(false)
|
|
||||||
val poll: MutableLiveData<NewPoll?> = mutableLiveData(null)
|
val statusVisibility: MutableStateFlow<Status.Visibility> = MutableStateFlow(Status.Visibility.UNKNOWN)
|
||||||
val scheduledAt: MutableLiveData<String?> = mutableLiveData(null)
|
val showContentWarning: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
val setupComplete: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
val poll: MutableStateFlow<NewPoll?> = MutableStateFlow(null)
|
||||||
|
val scheduledAt: MutableStateFlow<String?> = MutableStateFlow(null)
|
||||||
|
|
||||||
val media: MutableStateFlow<List<QueuedMedia>> = MutableStateFlow(emptyList())
|
val media: MutableStateFlow<List<QueuedMedia>> = MutableStateFlow(emptyList())
|
||||||
val uploadError = MutableLiveData<Throwable>()
|
val uploadError = MutableSharedFlow<Throwable>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||||
|
|
||||||
private val mediaToJob = mutableMapOf<Int, Job>()
|
private val mediaToJob = mutableMapOf<Int, Job>()
|
||||||
|
|
||||||
private val isEditingScheduledToot get() = !scheduledTootId.isNullOrEmpty()
|
|
||||||
|
|
||||||
// Used in ComposeActivity to pass state to result function when cropImage contract inflight
|
// Used in ComposeActivity to pass state to result function when cropImage contract inflight
|
||||||
var cropImageItemOld: QueuedMedia? = null
|
var cropImageItemOld: QueuedMedia? = null
|
||||||
|
|
||||||
init {
|
|
||||||
viewModelScope.launch {
|
|
||||||
emoji.postValue(instanceInfoRepo.getEmojis())
|
|
||||||
}
|
|
||||||
viewModelScope.launch {
|
|
||||||
instanceInfo.postValue(instanceInfoRepo.getInstanceInfo())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun pickMedia(mediaUri: Uri, description: String? = null): Result<QueuedMedia> = withContext(Dispatchers.IO) {
|
suspend fun pickMedia(mediaUri: Uri, description: String? = null): Result<QueuedMedia> = withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val (type, uri, size) = mediaUploader.prepareMedia(mediaUri)
|
val (type, uri, size) = mediaUploader.prepareMedia(mediaUri, instanceInfo.first())
|
||||||
val mediaItems = media.value
|
val mediaItems = media.value
|
||||||
if (type != QueuedMedia.Type.IMAGE &&
|
if (type != QueuedMedia.Type.IMAGE &&
|
||||||
mediaItems.isNotEmpty() &&
|
mediaItems.isNotEmpty() &&
|
||||||
|
@ -157,10 +150,10 @@ class ComposeViewModel @Inject constructor(
|
||||||
|
|
||||||
mediaToJob[mediaItem.localId] = viewModelScope.launch {
|
mediaToJob[mediaItem.localId] = viewModelScope.launch {
|
||||||
mediaUploader
|
mediaUploader
|
||||||
.uploadMedia(mediaItem)
|
.uploadMedia(mediaItem, instanceInfo.first())
|
||||||
.catch { error ->
|
.catch { error ->
|
||||||
media.update { mediaValue -> mediaValue.filter { it.localId != mediaItem.localId } }
|
media.update { mediaValue -> mediaValue.filter { it.localId != mediaItem.localId } }
|
||||||
uploadError.postValue(error)
|
uploadError.emit(error)
|
||||||
}
|
}
|
||||||
.collect { event ->
|
.collect { event ->
|
||||||
val item = media.value.find { it.localId == mediaItem.localId }
|
val item = media.value.find { it.localId == mediaItem.localId }
|
||||||
|
@ -216,7 +209,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
startingText?.startsWith(content.toString()) ?: false
|
startingText?.startsWith(content.toString()) ?: false
|
||||||
)
|
)
|
||||||
|
|
||||||
val contentWarningChanged = showContentWarning.value!! &&
|
val contentWarningChanged = showContentWarning.value &&
|
||||||
!contentWarning.isNullOrEmpty() &&
|
!contentWarning.isNullOrEmpty() &&
|
||||||
!startingContentWarning.startsWith(contentWarning.toString())
|
!startingContentWarning.startsWith(contentWarning.toString())
|
||||||
val mediaChanged = media.value.isNotEmpty()
|
val mediaChanged = media.value.isNotEmpty()
|
||||||
|
@ -259,8 +252,8 @@ class ComposeViewModel @Inject constructor(
|
||||||
inReplyToId = inReplyToId,
|
inReplyToId = inReplyToId,
|
||||||
content = content,
|
content = content,
|
||||||
contentWarning = contentWarning,
|
contentWarning = contentWarning,
|
||||||
sensitive = markMediaAsSensitive.value!!,
|
sensitive = markMediaAsSensitive.value,
|
||||||
visibility = statusVisibility.value!!,
|
visibility = statusVisibility.value,
|
||||||
mediaUris = mediaUris,
|
mediaUris = mediaUris,
|
||||||
mediaDescriptions = mediaDescriptions,
|
mediaDescriptions = mediaDescriptions,
|
||||||
poll = poll.value,
|
poll = poll.value,
|
||||||
|
@ -271,38 +264,34 @@ class ComposeViewModel @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Send status to the server.
|
* Send status to the server.
|
||||||
* Uses current state plus provided arguments.
|
* Uses current state plus provided arguments.
|
||||||
* @return LiveData which will signal once the screen can be closed or null if there are errors
|
|
||||||
*/
|
*/
|
||||||
fun sendStatus(
|
suspend fun sendStatus(
|
||||||
content: String,
|
content: String,
|
||||||
spoilerText: String
|
spoilerText: String
|
||||||
): LiveData<Unit> {
|
) {
|
||||||
|
|
||||||
val deletionObservable = if (isEditingScheduledToot) {
|
if (!scheduledTootId.isNullOrEmpty()) {
|
||||||
rxSingle { api.deleteScheduledStatus(scheduledTootId.toString()) }.toObservable().map { }
|
api.deleteScheduledStatus(scheduledTootId!!)
|
||||||
} else {
|
}
|
||||||
Observable.just(Unit)
|
|
||||||
}.toLiveData()
|
|
||||||
|
|
||||||
val sendFlow = media
|
media
|
||||||
.filter { items -> items.all { it.uploadPercent == -1 } }
|
.filter { items -> items.all { it.uploadPercent == -1 } }
|
||||||
.map {
|
.first {
|
||||||
val mediaIds: MutableList<String> = mutableListOf()
|
val mediaIds: MutableList<String> = mutableListOf()
|
||||||
val mediaUris: MutableList<Uri> = mutableListOf()
|
val mediaUris: MutableList<Uri> = mutableListOf()
|
||||||
val mediaDescriptions: MutableList<String> = mutableListOf()
|
val mediaDescriptions: MutableList<String> = mutableListOf()
|
||||||
val mediaProcessed: MutableList<Boolean> = mutableListOf()
|
val mediaProcessed: MutableList<Boolean> = mutableListOf()
|
||||||
for (item in media.value) {
|
media.value.forEach { item ->
|
||||||
mediaIds.add(item.id!!)
|
mediaIds.add(item.id!!)
|
||||||
mediaUris.add(item.uri)
|
mediaUris.add(item.uri)
|
||||||
mediaDescriptions.add(item.description ?: "")
|
mediaDescriptions.add(item.description ?: "")
|
||||||
mediaProcessed.add(false)
|
mediaProcessed.add(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val tootToSend = StatusToSend(
|
val tootToSend = StatusToSend(
|
||||||
text = content,
|
text = content,
|
||||||
warningText = spoilerText,
|
warningText = spoilerText,
|
||||||
visibility = statusVisibility.value!!.serverString(),
|
visibility = statusVisibility.value.serverString(),
|
||||||
sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!),
|
sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value || showContentWarning.value),
|
||||||
mediaIds = mediaIds,
|
mediaIds = mediaIds,
|
||||||
mediaUris = mediaUris.map { it.toString() },
|
mediaUris = mediaUris.map { it.toString() },
|
||||||
mediaDescriptions = mediaDescriptions,
|
mediaDescriptions = mediaDescriptions,
|
||||||
|
@ -319,9 +308,8 @@ class ComposeViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
serviceClient.sendToot(tootToSend)
|
serviceClient.sendToot(tootToSend)
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
return combineLiveData(deletionObservable, sendFlow.asLiveData()) { _, _ -> }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateDescription(localId: Int, description: String): Boolean {
|
suspend fun updateDescription(localId: Int, description: String): Boolean {
|
||||||
|
@ -369,7 +357,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
':' -> {
|
':' -> {
|
||||||
val emojiList = emoji.value ?: return emptyList()
|
val emojiList = emoji.replayCache.firstOrNull() ?: return emptyList()
|
||||||
val incomplete = token.substring(1)
|
val incomplete = token.substring(1)
|
||||||
|
|
||||||
return emojiList.filter { emoji ->
|
return emojiList.filter { emoji ->
|
||||||
|
@ -389,7 +377,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
|
|
||||||
fun setup(composeOptions: ComposeActivity.ComposeOptions?) {
|
fun setup(composeOptions: ComposeActivity.ComposeOptions?) {
|
||||||
|
|
||||||
if (setupComplete.value == true) {
|
if (setupComplete.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,8 +464,6 @@ class ComposeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> mutableLiveData(default: T) = MutableLiveData<T>().apply { value = default }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when trying to add an image when video is already present or the other way around
|
* Thrown when trying to add an image when video is already present or the other way around
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -27,6 +27,7 @@ import at.connyduck.calladapter.networkresult.fold
|
||||||
import com.keylesspalace.tusky.BuildConfig
|
import com.keylesspalace.tusky.BuildConfig
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
|
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
|
||||||
|
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfo
|
||||||
import com.keylesspalace.tusky.network.MediaUploadApi
|
import com.keylesspalace.tusky.network.MediaUploadApi
|
||||||
import com.keylesspalace.tusky.network.ProgressRequestBody
|
import com.keylesspalace.tusky.network.ProgressRequestBody
|
||||||
import com.keylesspalace.tusky.util.MEDIA_SIZE_UNKNOWN
|
import com.keylesspalace.tusky.util.MEDIA_SIZE_UNKNOWN
|
||||||
|
@ -82,10 +83,10 @@ class MediaUploader @Inject constructor(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun uploadMedia(media: QueuedMedia): Flow<UploadEvent> {
|
fun uploadMedia(media: QueuedMedia, instanceInfo: InstanceInfo): Flow<UploadEvent> {
|
||||||
return flow {
|
return flow {
|
||||||
if (shouldResizeMedia(media)) {
|
if (shouldResizeMedia(media, instanceInfo)) {
|
||||||
emit(downsize(media))
|
emit(downsize(media, instanceInfo))
|
||||||
} else {
|
} else {
|
||||||
emit(media)
|
emit(media)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +95,7 @@ class MediaUploader @Inject constructor(
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun prepareMedia(inUri: Uri): PreparedMedia {
|
fun prepareMedia(inUri: Uri, instanceInfo: InstanceInfo): PreparedMedia {
|
||||||
var mediaSize = MEDIA_SIZE_UNKNOWN
|
var mediaSize = MEDIA_SIZE_UNKNOWN
|
||||||
var uri = inUri
|
var uri = inUri
|
||||||
val mimeType: String?
|
val mimeType: String?
|
||||||
|
@ -164,7 +165,7 @@ class MediaUploader @Inject constructor(
|
||||||
if (mimeType != null) {
|
if (mimeType != null) {
|
||||||
return when (mimeType.substring(0, mimeType.indexOf('/'))) {
|
return when (mimeType.substring(0, mimeType.indexOf('/'))) {
|
||||||
"video" -> {
|
"video" -> {
|
||||||
if (mediaSize > STATUS_VIDEO_SIZE_LIMIT) {
|
if (mediaSize > instanceInfo.videoSizeLimit) {
|
||||||
throw VideoSizeException()
|
throw VideoSizeException()
|
||||||
}
|
}
|
||||||
PreparedMedia(QueuedMedia.Type.VIDEO, uri, mediaSize)
|
PreparedMedia(QueuedMedia.Type.VIDEO, uri, mediaSize)
|
||||||
|
@ -173,7 +174,7 @@ class MediaUploader @Inject constructor(
|
||||||
PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize)
|
PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize)
|
||||||
}
|
}
|
||||||
"audio" -> {
|
"audio" -> {
|
||||||
if (mediaSize > STATUS_AUDIO_SIZE_LIMIT) {
|
if (mediaSize > instanceInfo.videoSizeLimit) {
|
||||||
throw AudioSizeException()
|
throw AudioSizeException()
|
||||||
}
|
}
|
||||||
PreparedMedia(QueuedMedia.Type.AUDIO, uri, mediaSize)
|
PreparedMedia(QueuedMedia.Type.AUDIO, uri, mediaSize)
|
||||||
|
@ -239,22 +240,18 @@ class MediaUploader @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downsize(media: QueuedMedia): QueuedMedia {
|
private fun downsize(media: QueuedMedia, instanceInfo: InstanceInfo): QueuedMedia {
|
||||||
val file = createNewImageFile(context)
|
val file = createNewImageFile(context)
|
||||||
downsizeImage(media.uri, STATUS_IMAGE_SIZE_LIMIT, contentResolver, file)
|
downsizeImage(media.uri, instanceInfo.imageSizeLimit, contentResolver, file)
|
||||||
return media.copy(uri = file.toUri(), mediaSize = file.length())
|
return media.copy(uri = file.toUri(), mediaSize = file.length())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldResizeMedia(media: QueuedMedia): Boolean {
|
private fun shouldResizeMedia(media: QueuedMedia, instanceInfo: InstanceInfo): Boolean {
|
||||||
return media.type == QueuedMedia.Type.IMAGE &&
|
return media.type == QueuedMedia.Type.IMAGE &&
|
||||||
(media.mediaSize > STATUS_IMAGE_SIZE_LIMIT || getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)
|
(media.mediaSize > instanceInfo.imageSizeLimit || getImageSquarePixels(context.contentResolver, media.uri) > instanceInfo.imageMatrixLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val TAG = "MediaUploader"
|
private const val TAG = "MediaUploader"
|
||||||
private const val STATUS_VIDEO_SIZE_LIMIT = 41943040 // 40MiB
|
|
||||||
private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB
|
|
||||||
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB
|
|
||||||
private const val STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,5 +21,12 @@ data class InstanceInfo(
|
||||||
val pollMaxLength: Int,
|
val pollMaxLength: Int,
|
||||||
val pollMinDuration: Int,
|
val pollMinDuration: Int,
|
||||||
val pollMaxDuration: Int,
|
val pollMaxDuration: Int,
|
||||||
val charactersReservedPerUrl: Int
|
val charactersReservedPerUrl: Int,
|
||||||
|
val videoSizeLimit: Int,
|
||||||
|
val imageSizeLimit: Int,
|
||||||
|
val imageMatrixLimit: Int,
|
||||||
|
val maxMediaAttachments: Int,
|
||||||
|
val maxFields: Int,
|
||||||
|
val maxFieldNameLength: Int?,
|
||||||
|
val maxFieldValueLength: Int?
|
||||||
)
|
)
|
||||||
|
|
|
@ -69,7 +69,14 @@ class InstanceInfoRepository @Inject constructor(
|
||||||
minPollDuration = instance.configuration?.polls?.minExpiration ?: instance.pollConfiguration?.minExpiration,
|
minPollDuration = instance.configuration?.polls?.minExpiration ?: instance.pollConfiguration?.minExpiration,
|
||||||
maxPollDuration = instance.configuration?.polls?.maxExpiration ?: instance.pollConfiguration?.maxExpiration,
|
maxPollDuration = instance.configuration?.polls?.maxExpiration ?: instance.pollConfiguration?.maxExpiration,
|
||||||
charactersReservedPerUrl = instance.configuration?.statuses?.charactersReservedPerUrl,
|
charactersReservedPerUrl = instance.configuration?.statuses?.charactersReservedPerUrl,
|
||||||
version = instance.version
|
version = instance.version,
|
||||||
|
videoSizeLimit = instance.configuration?.mediaAttachments?.videoSizeLimit,
|
||||||
|
imageSizeLimit = instance.configuration?.mediaAttachments?.imageSizeLimit,
|
||||||
|
imageMatrixLimit = instance.configuration?.mediaAttachments?.imageMatrixLimit,
|
||||||
|
maxMediaAttachments = instance.configuration?.statuses?.maxMediaAttachments ?: instance.maxMediaAttachments,
|
||||||
|
maxFields = instance.pleroma?.metadata?.fieldLimits?.maxFields,
|
||||||
|
maxFieldNameLength = instance.pleroma?.metadata?.fieldLimits?.nameLength,
|
||||||
|
maxFieldValueLength = instance.pleroma?.metadata?.fieldLimits?.valueLength
|
||||||
)
|
)
|
||||||
dao.insertOrReplace(instanceEntity)
|
dao.insertOrReplace(instanceEntity)
|
||||||
instanceEntity
|
instanceEntity
|
||||||
|
@ -85,7 +92,14 @@ class InstanceInfoRepository @Inject constructor(
|
||||||
pollMaxLength = instanceInfo?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH,
|
pollMaxLength = instanceInfo?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH,
|
||||||
pollMinDuration = instanceInfo?.minPollDuration ?: DEFAULT_MIN_POLL_DURATION,
|
pollMinDuration = instanceInfo?.minPollDuration ?: DEFAULT_MIN_POLL_DURATION,
|
||||||
pollMaxDuration = instanceInfo?.maxPollDuration ?: DEFAULT_MAX_POLL_DURATION,
|
pollMaxDuration = instanceInfo?.maxPollDuration ?: DEFAULT_MAX_POLL_DURATION,
|
||||||
charactersReservedPerUrl = instanceInfo?.charactersReservedPerUrl ?: DEFAULT_CHARACTERS_RESERVED_PER_URL
|
charactersReservedPerUrl = instanceInfo?.charactersReservedPerUrl ?: DEFAULT_CHARACTERS_RESERVED_PER_URL,
|
||||||
|
videoSizeLimit = instanceInfo?.videoSizeLimit ?: DEFAULT_VIDEO_SIZE_LIMIT,
|
||||||
|
imageSizeLimit = instanceInfo?.imageSizeLimit ?: DEFAULT_IMAGE_SIZE_LIMIT,
|
||||||
|
imageMatrixLimit = instanceInfo?.imageMatrixLimit ?: DEFAULT_IMAGE_MATRIX_LIMIT,
|
||||||
|
maxMediaAttachments = instanceInfo?.maxMediaAttachments ?: DEFAULT_MAX_MEDIA_ATTACHMENTS,
|
||||||
|
maxFields = instanceInfo?.maxFields ?: DEFAULT_MAX_ACCOUNT_FIELDS,
|
||||||
|
maxFieldNameLength = instanceInfo?.maxFieldNameLength,
|
||||||
|
maxFieldValueLength = instanceInfo?.maxFieldValueLength
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +113,14 @@ class InstanceInfoRepository @Inject constructor(
|
||||||
private const val DEFAULT_MIN_POLL_DURATION = 300
|
private const val DEFAULT_MIN_POLL_DURATION = 300
|
||||||
private const val DEFAULT_MAX_POLL_DURATION = 604800
|
private const val DEFAULT_MAX_POLL_DURATION = 604800
|
||||||
|
|
||||||
|
private const val DEFAULT_VIDEO_SIZE_LIMIT = 41943040 // 40MiB
|
||||||
|
private const val DEFAULT_IMAGE_SIZE_LIMIT = 10485760 // 10MiB
|
||||||
|
private const val DEFAULT_IMAGE_MATRIX_LIMIT = 16777216 // 4096^2 Pixels
|
||||||
|
|
||||||
// Mastodon only counts URLs as this long in terms of status character limits
|
// Mastodon only counts URLs as this long in terms of status character limits
|
||||||
const val DEFAULT_CHARACTERS_RESERVED_PER_URL = 23
|
const val DEFAULT_CHARACTERS_RESERVED_PER_URL = 23
|
||||||
|
|
||||||
|
const val DEFAULT_MAX_MEDIA_ATTACHMENTS = 4
|
||||||
|
const val DEFAULT_MAX_ACCOUNT_FIELDS = 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import java.io.File;
|
||||||
*/
|
*/
|
||||||
@Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
|
@Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
|
||||||
TimelineAccountEntity.class, ConversationEntity.class
|
TimelineAccountEntity.class, ConversationEntity.class
|
||||||
}, version = 39)
|
}, version = 40)
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
|
|
||||||
public abstract AccountDao accountDao();
|
public abstract AccountDao accountDao();
|
||||||
|
@ -581,4 +581,17 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||||
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `clientSecret` TEXT");
|
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `clientSecret` TEXT");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static final Migration MIGRATION_39_40 = new Migration(39, 40) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `videoSizeLimit` INTEGER");
|
||||||
|
database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `imageSizeLimit` INTEGER");
|
||||||
|
database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `imageMatrixLimit` INTEGER");
|
||||||
|
database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `maxMediaAttachments` INTEGER");
|
||||||
|
database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `maxFields` INTEGER");
|
||||||
|
database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `maxFieldNameLength` INTEGER");
|
||||||
|
database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `maxFieldValueLength` INTEGER");
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,14 @@ data class InstanceEntity(
|
||||||
val minPollDuration: Int?,
|
val minPollDuration: Int?,
|
||||||
val maxPollDuration: Int?,
|
val maxPollDuration: Int?,
|
||||||
val charactersReservedPerUrl: Int?,
|
val charactersReservedPerUrl: Int?,
|
||||||
val version: String?
|
val version: String?,
|
||||||
|
val videoSizeLimit: Int?,
|
||||||
|
val imageSizeLimit: Int?,
|
||||||
|
val imageMatrixLimit: Int?,
|
||||||
|
val maxMediaAttachments: Int?,
|
||||||
|
val maxFields: Int?,
|
||||||
|
val maxFieldNameLength: Int?,
|
||||||
|
val maxFieldValueLength: Int?
|
||||||
)
|
)
|
||||||
|
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
|
@ -48,5 +55,12 @@ data class InstanceInfoEntity(
|
||||||
val minPollDuration: Int?,
|
val minPollDuration: Int?,
|
||||||
val maxPollDuration: Int?,
|
val maxPollDuration: Int?,
|
||||||
val charactersReservedPerUrl: Int?,
|
val charactersReservedPerUrl: Int?,
|
||||||
val version: String?
|
val version: String?,
|
||||||
|
val videoSizeLimit: Int?,
|
||||||
|
val imageSizeLimit: Int?,
|
||||||
|
val imageMatrixLimit: Int?,
|
||||||
|
val maxMediaAttachments: Int?,
|
||||||
|
val maxFields: Int?,
|
||||||
|
val maxFieldNameLength: Int?,
|
||||||
|
val maxFieldValueLength: Int?
|
||||||
)
|
)
|
||||||
|
|
|
@ -65,7 +65,7 @@ class AppModule {
|
||||||
AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32,
|
AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32,
|
||||||
AppDatabase.MIGRATION_32_33, AppDatabase.MIGRATION_33_34, AppDatabase.MIGRATION_34_35,
|
AppDatabase.MIGRATION_32_33, AppDatabase.MIGRATION_33_34, AppDatabase.MIGRATION_34_35,
|
||||||
AppDatabase.MIGRATION_35_36, AppDatabase.MIGRATION_36_37, AppDatabase.MIGRATION_37_38,
|
AppDatabase.MIGRATION_35_36, AppDatabase.MIGRATION_36_37, AppDatabase.MIGRATION_37_38,
|
||||||
AppDatabase.MIGRATION_38_39
|
AppDatabase.MIGRATION_38_39, AppDatabase.MIGRATION_39_40
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,19 +19,20 @@ import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
data class Instance(
|
data class Instance(
|
||||||
val uri: String,
|
val uri: String,
|
||||||
val title: String,
|
// val title: String,
|
||||||
val description: String,
|
// val description: String,
|
||||||
val email: String,
|
// val email: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
val urls: Map<String, String>,
|
// val urls: Map<String, String>,
|
||||||
val stats: Map<String, Int>?,
|
// val stats: Map<String, Int>?,
|
||||||
val thumbnail: String?,
|
// val thumbnail: String?,
|
||||||
val languages: List<String>,
|
// val languages: List<String>,
|
||||||
@SerializedName("contact_account") val contactAccount: Account,
|
// @SerializedName("contact_account") val contactAccount: Account,
|
||||||
@SerializedName("max_toot_chars") val maxTootChars: Int?,
|
@SerializedName("max_toot_chars") val maxTootChars: Int?,
|
||||||
@SerializedName("max_bio_chars") val maxBioChars: Int?,
|
|
||||||
@SerializedName("poll_limits") val pollConfiguration: PollConfiguration?,
|
@SerializedName("poll_limits") val pollConfiguration: PollConfiguration?,
|
||||||
val configuration: InstanceConfiguration?,
|
val configuration: InstanceConfiguration?,
|
||||||
|
@SerializedName("max_media_attachments") val maxMediaAttachments: Int?,
|
||||||
|
val pleroma: PleromaConfiguration?
|
||||||
) {
|
) {
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return uri.hashCode()
|
return uri.hashCode()
|
||||||
|
@ -74,3 +75,17 @@ data class MediaAttachmentConfiguration(
|
||||||
@SerializedName("video_frame_rate_limit") val videoFrameRateLimit: Int?,
|
@SerializedName("video_frame_rate_limit") val videoFrameRateLimit: Int?,
|
||||||
@SerializedName("video_matrix_limit") val videoMatrixLimit: Int?,
|
@SerializedName("video_matrix_limit") val videoMatrixLimit: Int?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class PleromaConfiguration(
|
||||||
|
val metadata: PleromaMetadata?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PleromaMetadata(
|
||||||
|
@SerializedName("fields_limits") val fieldLimits: PleromaFieldLimits
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PleromaFieldLimits(
|
||||||
|
@SerializedName("max_fields") val maxFields: Int?,
|
||||||
|
@SerializedName("name_length") val nameLength: Int?,
|
||||||
|
@SerializedName("value_length") val valueLength: Int?
|
||||||
|
)
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
/* Copyright 2019 Tusky Contributors
|
|
||||||
*
|
|
||||||
* This file is a part of Tusky.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
|
||||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
||||||
* Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky.util
|
|
||||||
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.LiveDataReactiveStreams
|
|
||||||
import androidx.lifecycle.MediatorLiveData
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.lifecycle.Transformations
|
|
||||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
|
||||||
import io.reactivex.rxjava3.core.Observable
|
|
||||||
import io.reactivex.rxjava3.core.Single
|
|
||||||
|
|
||||||
inline fun <X, Y> LiveData<X>.map(crossinline mapFunction: (X) -> Y): LiveData<Y> =
|
|
||||||
Transformations.map(this) { input -> mapFunction(input) }
|
|
||||||
|
|
||||||
inline fun <X, Y> LiveData<X>.switchMap(
|
|
||||||
crossinline switchMapFunction: (X) -> LiveData<Y>
|
|
||||||
): LiveData<Y> = Transformations.switchMap(this) { input -> switchMapFunction(input) }
|
|
||||||
|
|
||||||
inline fun <X> LiveData<X>.filter(crossinline predicate: (X) -> Boolean): LiveData<X> {
|
|
||||||
val liveData = MediatorLiveData<X>()
|
|
||||||
liveData.addSource(this) { value ->
|
|
||||||
if (predicate(value)) {
|
|
||||||
liveData.value = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return liveData
|
|
||||||
}
|
|
||||||
|
|
||||||
fun LifecycleOwner.withLifecycleContext(body: LifecycleContext.() -> Unit) =
|
|
||||||
LifecycleContext(this).apply(body)
|
|
||||||
|
|
||||||
class LifecycleContext(val lifecycleOwner: LifecycleOwner) {
|
|
||||||
inline fun <T> LiveData<T>.observe(crossinline observer: (T) -> Unit) =
|
|
||||||
this.observe(lifecycleOwner, Observer { observer(it) })
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Just hold a subscription,
|
|
||||||
*/
|
|
||||||
fun <T> LiveData<T>.subscribe() =
|
|
||||||
this.observe(lifecycleOwner, Observer { })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes @param [combiner] when value of both @param [a] and @param [b] are not null. Returns
|
|
||||||
* [LiveData] with value set to the result of calling [combiner] with value of both.
|
|
||||||
* Important! You still need to observe to the returned [LiveData] for [combiner] to be invoked.
|
|
||||||
*/
|
|
||||||
fun <A, B, R> combineLiveData(a: LiveData<A>, b: LiveData<B>, combiner: (A, B) -> R): LiveData<R> {
|
|
||||||
val liveData = MediatorLiveData<R>()
|
|
||||||
liveData.addSource(a) {
|
|
||||||
if (a.value != null && b.value != null) {
|
|
||||||
liveData.value = combiner(a.value!!, b.value!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
liveData.addSource(b) {
|
|
||||||
if (a.value != null && b.value != null) {
|
|
||||||
liveData.value = combiner(a.value!!, b.value!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return liveData
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns [LiveData] with value set to the result of calling [combiner] with value of [a] and [b]
|
|
||||||
* after either changes. Doesn't check if either has value.
|
|
||||||
* Important! You still need to observe to the returned [LiveData] for [combiner] to be invoked.
|
|
||||||
*/
|
|
||||||
fun <A, B, R> combineOptionalLiveData(a: LiveData<A>, b: LiveData<B>, combiner: (A?, B?) -> R): LiveData<R> {
|
|
||||||
val liveData = MediatorLiveData<R>()
|
|
||||||
liveData.addSource(a) {
|
|
||||||
liveData.value = combiner(a.value, b.value)
|
|
||||||
}
|
|
||||||
liveData.addSource(b) {
|
|
||||||
liveData.value = combiner(a.value, b.value)
|
|
||||||
}
|
|
||||||
return liveData
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> Single<T>.toLiveData() = LiveDataReactiveStreams.fromPublisher(this.toFlowable())
|
|
||||||
fun <T> Observable<T>.toLiveData(
|
|
||||||
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
|
|
||||||
) = LiveDataReactiveStreams.fromPublisher(this.toFlowable(BackpressureStrategy.LATEST))
|
|
|
@ -24,8 +24,9 @@ import androidx.lifecycle.viewModelScope
|
||||||
import at.connyduck.calladapter.networkresult.fold
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import com.keylesspalace.tusky.appstore.EventHub
|
import com.keylesspalace.tusky.appstore.EventHub
|
||||||
import com.keylesspalace.tusky.appstore.ProfileEditedEvent
|
import com.keylesspalace.tusky.appstore.ProfileEditedEvent
|
||||||
|
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfo
|
||||||
|
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
|
||||||
import com.keylesspalace.tusky.entity.Account
|
import com.keylesspalace.tusky.entity.Account
|
||||||
import com.keylesspalace.tusky.entity.Instance
|
|
||||||
import com.keylesspalace.tusky.entity.StringField
|
import com.keylesspalace.tusky.entity.StringField
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.util.Error
|
import com.keylesspalace.tusky.util.Error
|
||||||
|
@ -34,6 +35,11 @@ import com.keylesspalace.tusky.util.Resource
|
||||||
import com.keylesspalace.tusky.util.Success
|
import com.keylesspalace.tusky.util.Success
|
||||||
import com.keylesspalace.tusky.util.getServerErrorMessage
|
import com.keylesspalace.tusky.util.getServerErrorMessage
|
||||||
import com.keylesspalace.tusky.util.randomAlphanumericString
|
import com.keylesspalace.tusky.util.randomAlphanumericString
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
|
@ -49,14 +55,18 @@ private const val AVATAR_FILE_NAME = "avatar.png"
|
||||||
class EditProfileViewModel @Inject constructor(
|
class EditProfileViewModel @Inject constructor(
|
||||||
private val mastodonApi: MastodonApi,
|
private val mastodonApi: MastodonApi,
|
||||||
private val eventHub: EventHub,
|
private val eventHub: EventHub,
|
||||||
private val application: Application
|
private val application: Application,
|
||||||
|
private val instanceInfoRepo: InstanceInfoRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val profileData = MutableLiveData<Resource<Account>>()
|
val profileData = MutableLiveData<Resource<Account>>()
|
||||||
val avatarData = MutableLiveData<Uri>()
|
val avatarData = MutableLiveData<Uri>()
|
||||||
val headerData = MutableLiveData<Uri>()
|
val headerData = MutableLiveData<Uri>()
|
||||||
val saveData = MutableLiveData<Resource<Nothing>>()
|
val saveData = MutableLiveData<Resource<Nothing>>()
|
||||||
val instanceData = MutableLiveData<Resource<Instance>>()
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
val instanceData: Flow<InstanceInfo> = instanceInfoRepo::getInstanceInfo.asFlow()
|
||||||
|
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||||
|
|
||||||
private var oldProfileData: Account? = null
|
private var oldProfileData: Account? = null
|
||||||
|
|
||||||
|
@ -186,19 +196,4 @@ class EditProfileViewModel @Inject constructor(
|
||||||
private fun getCacheFileForName(filename: String): File {
|
private fun getCacheFileForName(filename: String): File {
|
||||||
return File(application.cacheDir, filename)
|
return File(application.cacheDir, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun obtainInstance() = viewModelScope.launch {
|
|
||||||
if (instanceData.value == null || instanceData.value is Error) {
|
|
||||||
instanceData.postValue(Loading())
|
|
||||||
|
|
||||||
mastodonApi.getInstance().fold(
|
|
||||||
{ instance ->
|
|
||||||
instanceData.postValue(Success(instance))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instanceData.postValue(Error())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
@ -17,23 +18,40 @@
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
android:paddingBottom="8dp">
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
<androidx.emoji2.widget.EmojiEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/accountFieldName"
|
style="@style/TuskyTextInput"
|
||||||
|
android:id="@+id/accountFieldNameTextLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
|
||||||
android:hint="@string/profile_metadata_label_label"
|
android:hint="@string/profile_metadata_label_label"
|
||||||
android:textColorHint="?android:attr/textColorTertiary"
|
app:counterTextColor="?android:textColorTertiary">
|
||||||
android:textSize="?attr/status_text_medium" />
|
|
||||||
|
|
||||||
<androidx.emoji2.widget.EmojiEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/accountFieldValue"
|
android:id="@+id/accountFieldNameText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:importantForAutofill="no" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
style="@style/TuskyTextInput"
|
||||||
|
android:id="@+id/accountFieldValueTextLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
android:hint="@string/profile_metadata_content_label"
|
android:hint="@string/profile_metadata_content_label"
|
||||||
android:lineSpacingMultiplier="1.1"
|
app:counterTextColor="?android:textColorTertiary">
|
||||||
android:textColorHint="?android:attr/textColorTertiary"
|
|
||||||
android:textSize="?attr/status_text_medium" />
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/accountFieldValueText"
|
||||||
|
android:lineSpacingMultiplier="1.1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:importantForAutofill="no" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
|
@ -30,7 +30,6 @@ import com.keylesspalace.tusky.db.EmojisEntity
|
||||||
import com.keylesspalace.tusky.db.InstanceDao
|
import com.keylesspalace.tusky.db.InstanceDao
|
||||||
import com.keylesspalace.tusky.db.InstanceInfoEntity
|
import com.keylesspalace.tusky.db.InstanceInfoEntity
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.entity.Account
|
|
||||||
import com.keylesspalace.tusky.entity.Instance
|
import com.keylesspalace.tusky.entity.Instance
|
||||||
import com.keylesspalace.tusky.entity.InstanceConfiguration
|
import com.keylesspalace.tusky.entity.InstanceConfiguration
|
||||||
import com.keylesspalace.tusky.entity.StatusConfiguration
|
import com.keylesspalace.tusky.entity.StatusConfiguration
|
||||||
|
@ -48,8 +47,6 @@ import org.robolectric.Robolectric
|
||||||
import org.robolectric.Shadows.shadowOf
|
import org.robolectric.Shadows.shadowOf
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
import org.robolectric.fakes.RoboMenuItem
|
import org.robolectric.fakes.RoboMenuItem
|
||||||
import java.util.Date
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by charlag on 3/7/18.
|
* Created by charlag on 3/7/18.
|
||||||
|
@ -110,7 +107,7 @@ class ComposeActivityTest {
|
||||||
|
|
||||||
val instanceDaoMock: InstanceDao = mock {
|
val instanceDaoMock: InstanceDao = mock {
|
||||||
onBlocking { getInstanceInfo(any()) } doReturn
|
onBlocking { getInstanceInfo(any()) } doReturn
|
||||||
InstanceInfoEntity(instanceDomain, null, null, null, null, null, null, null)
|
InstanceInfoEntity(instanceDomain, null, null, null, null, null, null, null, null, null, null, null, null, null, null)
|
||||||
onBlocking { getEmojiInfo(any()) } doReturn
|
onBlocking { getEmojiInfo(any()) } doReturn
|
||||||
EmojisEntity(instanceDomain, emptyList())
|
EmojisEntity(instanceDomain, emptyList())
|
||||||
}
|
}
|
||||||
|
@ -461,38 +458,13 @@ class ComposeActivityTest {
|
||||||
|
|
||||||
private fun getInstanceWithCustomConfiguration(maximumLegacyTootCharacters: Int? = null, configuration: InstanceConfiguration? = null): Instance {
|
private fun getInstanceWithCustomConfiguration(maximumLegacyTootCharacters: Int? = null, configuration: InstanceConfiguration? = null): Instance {
|
||||||
return Instance(
|
return Instance(
|
||||||
"https://example.token",
|
uri = "https://example.token",
|
||||||
"Example dot Token",
|
version = "2.6.3",
|
||||||
"Example instance for testing",
|
maxTootChars = maximumLegacyTootCharacters,
|
||||||
"admin@example.token",
|
pollConfiguration = null,
|
||||||
"2.6.3",
|
configuration = configuration,
|
||||||
HashMap(),
|
maxMediaAttachments = null,
|
||||||
null,
|
pleroma = null
|
||||||
null,
|
|
||||||
listOf("en"),
|
|
||||||
Account(
|
|
||||||
id = "1",
|
|
||||||
localUsername = "admin",
|
|
||||||
username = "admin",
|
|
||||||
displayName = "admin",
|
|
||||||
createdAt = Date(),
|
|
||||||
note = "",
|
|
||||||
url = "https://example.token",
|
|
||||||
avatar = "",
|
|
||||||
header = "",
|
|
||||||
locked = false,
|
|
||||||
statusesCount = 0,
|
|
||||||
followersCount = 0,
|
|
||||||
followingCount = 0,
|
|
||||||
source = null,
|
|
||||||
bot = false,
|
|
||||||
emojis = emptyList(),
|
|
||||||
fields = emptyList(),
|
|
||||||
),
|
|
||||||
maximumLegacyTootCharacters,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
configuration,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue