Merge branch 'arrange_tabs' into 'master'

Rearrange tabs

See merge request pixeldroid/PixelDroid!570
This commit is contained in:
Matthieu 2024-08-19 20:10:32 +00:00
commit 34d2a8c581
20 changed files with 2200 additions and 131 deletions

View File

@ -50,6 +50,9 @@ android {
versionName "1.0.beta" + versionCode
//TODO add resConfigs("en", "fr", "ja",...) ?
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
testInstrumentationRunner "org.pixeldroid.app.testUtility.TestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'

View File

@ -0,0 +1,710 @@
{
"formatVersion": 1,
"database": {
"version": 6,
"identityHash": "365491e03fadf81e596b11716640518c",
"entities": [
{
"tableName": "instances",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uri` TEXT NOT NULL, `title` TEXT NOT NULL, `maxStatusChars` INTEGER NOT NULL, `maxPhotoSize` INTEGER NOT NULL, `maxVideoSize` INTEGER NOT NULL, `albumLimit` INTEGER NOT NULL, `videoEnabled` INTEGER NOT NULL, `pixelfed` INTEGER NOT NULL, PRIMARY KEY(`uri`))",
"fields": [
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "maxStatusChars",
"columnName": "maxStatusChars",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "maxPhotoSize",
"columnName": "maxPhotoSize",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "maxVideoSize",
"columnName": "maxVideoSize",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "albumLimit",
"columnName": "albumLimit",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "videoEnabled",
"columnName": "videoEnabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pixelfed",
"columnName": "pixelfed",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"uri"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "users",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `instance_uri` TEXT NOT NULL, `username` TEXT NOT NULL, `display_name` TEXT NOT NULL, `avatar_static` TEXT NOT NULL, `isActive` INTEGER NOT NULL, `accessToken` TEXT NOT NULL, `refreshToken` TEXT, `clientId` TEXT NOT NULL, `clientSecret` TEXT NOT NULL, PRIMARY KEY(`user_id`, `instance_uri`), FOREIGN KEY(`instance_uri`) REFERENCES `instances`(`uri`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "user_id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instance_uri",
"columnName": "instance_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "display_name",
"columnName": "display_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "avatar_static",
"columnName": "avatar_static",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isActive",
"columnName": "isActive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accessToken",
"columnName": "accessToken",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "refreshToken",
"columnName": "refreshToken",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "clientId",
"columnName": "clientId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "clientSecret",
"columnName": "clientSecret",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"user_id",
"instance_uri"
]
},
"indices": [
{
"name": "index_users_instance_uri",
"unique": false,
"columnNames": [
"instance_uri"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_users_instance_uri` ON `${TABLE_NAME}` (`instance_uri`)"
}
],
"foreignKeys": [
{
"table": "instances",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"instance_uri"
],
"referencedColumns": [
"uri"
]
}
]
},
{
"tableName": "homePosts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `instance_uri` TEXT NOT NULL, `id` TEXT NOT NULL, `uri` TEXT, `created_at` TEXT, `account` TEXT, `content` TEXT, `visibility` TEXT, `sensitive` INTEGER, `spoiler_text` TEXT, `media_attachments` TEXT, `application` TEXT, `mentions` TEXT, `tags` TEXT, `emojis` TEXT, `reblogs_count` INTEGER, `favourites_count` INTEGER, `replies_count` INTEGER, `url` TEXT, `in_reply_to_id` TEXT, `in_reply_to_account` TEXT, `reblog` TEXT, `poll` TEXT, `card` TEXT, `language` TEXT, `text` TEXT, `favourited` INTEGER, `reblogged` INTEGER, `muted` INTEGER, `bookmarked` INTEGER, `pinned` INTEGER, PRIMARY KEY(`id`, `user_id`, `instance_uri`), FOREIGN KEY(`user_id`, `instance_uri`) REFERENCES `users`(`user_id`, `instance_uri`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "user_id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instance_uri",
"columnName": "instance_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "created_at",
"columnName": "created_at",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "account",
"columnName": "account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "visibility",
"columnName": "visibility",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sensitive",
"columnName": "sensitive",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "spoiler_text",
"columnName": "spoiler_text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "media_attachments",
"columnName": "media_attachments",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "application",
"columnName": "application",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mentions",
"columnName": "mentions",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "emojis",
"columnName": "emojis",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "reblogs_count",
"columnName": "reblogs_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "favourites_count",
"columnName": "favourites_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "replies_count",
"columnName": "replies_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "in_reply_to_id",
"columnName": "in_reply_to_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "in_reply_to_account",
"columnName": "in_reply_to_account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "reblog",
"columnName": "reblog",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "poll",
"columnName": "poll",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "card",
"columnName": "card",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "language",
"columnName": "language",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "favourited",
"columnName": "favourited",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "reblogged",
"columnName": "reblogged",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "muted",
"columnName": "muted",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bookmarked",
"columnName": "bookmarked",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "pinned",
"columnName": "pinned",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"user_id",
"instance_uri"
]
},
"indices": [
{
"name": "index_homePosts_user_id_instance_uri",
"unique": false,
"columnNames": [
"user_id",
"instance_uri"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_homePosts_user_id_instance_uri` ON `${TABLE_NAME}` (`user_id`, `instance_uri`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"user_id",
"instance_uri"
],
"referencedColumns": [
"user_id",
"instance_uri"
]
}
]
},
{
"tableName": "publicPosts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `instance_uri` TEXT NOT NULL, `id` TEXT NOT NULL, `uri` TEXT, `created_at` TEXT, `account` TEXT, `content` TEXT, `visibility` TEXT, `sensitive` INTEGER, `spoiler_text` TEXT, `media_attachments` TEXT, `application` TEXT, `mentions` TEXT, `tags` TEXT, `emojis` TEXT, `reblogs_count` INTEGER, `favourites_count` INTEGER, `replies_count` INTEGER, `url` TEXT, `in_reply_to_id` TEXT, `in_reply_to_account` TEXT, `reblog` TEXT, `poll` TEXT, `card` TEXT, `language` TEXT, `text` TEXT, `favourited` INTEGER, `reblogged` INTEGER, `muted` INTEGER, `bookmarked` INTEGER, `pinned` INTEGER, PRIMARY KEY(`id`, `user_id`, `instance_uri`), FOREIGN KEY(`user_id`, `instance_uri`) REFERENCES `users`(`user_id`, `instance_uri`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "user_id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instance_uri",
"columnName": "instance_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "created_at",
"columnName": "created_at",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "account",
"columnName": "account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "visibility",
"columnName": "visibility",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sensitive",
"columnName": "sensitive",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "spoiler_text",
"columnName": "spoiler_text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "media_attachments",
"columnName": "media_attachments",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "application",
"columnName": "application",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mentions",
"columnName": "mentions",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "emojis",
"columnName": "emojis",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "reblogs_count",
"columnName": "reblogs_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "favourites_count",
"columnName": "favourites_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "replies_count",
"columnName": "replies_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "in_reply_to_id",
"columnName": "in_reply_to_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "in_reply_to_account",
"columnName": "in_reply_to_account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "reblog",
"columnName": "reblog",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "poll",
"columnName": "poll",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "card",
"columnName": "card",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "language",
"columnName": "language",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "favourited",
"columnName": "favourited",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "reblogged",
"columnName": "reblogged",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "muted",
"columnName": "muted",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bookmarked",
"columnName": "bookmarked",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "pinned",
"columnName": "pinned",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"user_id",
"instance_uri"
]
},
"indices": [
{
"name": "index_publicPosts_user_id_instance_uri",
"unique": false,
"columnNames": [
"user_id",
"instance_uri"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_publicPosts_user_id_instance_uri` ON `${TABLE_NAME}` (`user_id`, `instance_uri`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"user_id",
"instance_uri"
],
"referencedColumns": [
"user_id",
"instance_uri"
]
}
]
},
{
"tableName": "notifications",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` TEXT, `created_at` TEXT, `account` TEXT, `status` TEXT, `user_id` TEXT NOT NULL, `instance_uri` TEXT NOT NULL, PRIMARY KEY(`id`, `user_id`, `instance_uri`), FOREIGN KEY(`user_id`, `instance_uri`) REFERENCES `users`(`user_id`, `instance_uri`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "created_at",
"columnName": "created_at",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "account",
"columnName": "account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "user_id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instance_uri",
"columnName": "instance_uri",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"user_id",
"instance_uri"
]
},
"indices": [
{
"name": "index_notifications_user_id_instance_uri",
"unique": false,
"columnNames": [
"user_id",
"instance_uri"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_notifications_user_id_instance_uri` ON `${TABLE_NAME}` (`user_id`, `instance_uri`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"user_id",
"instance_uri"
],
"referencedColumns": [
"user_id",
"instance_uri"
]
}
]
}
],
"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, '365491e03fadf81e596b11716640518c')"
]
}
}

View File

@ -0,0 +1,781 @@
{
"formatVersion": 1,
"database": {
"version": 7,
"identityHash": "cd0310b10eff0e3e961b3cd7a8172b81",
"entities": [
{
"tableName": "instances",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uri` TEXT NOT NULL, `title` TEXT NOT NULL, `maxStatusChars` INTEGER NOT NULL, `maxPhotoSize` INTEGER NOT NULL, `maxVideoSize` INTEGER NOT NULL, `albumLimit` INTEGER NOT NULL, `videoEnabled` INTEGER NOT NULL, `pixelfed` INTEGER NOT NULL, PRIMARY KEY(`uri`))",
"fields": [
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "maxStatusChars",
"columnName": "maxStatusChars",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "maxPhotoSize",
"columnName": "maxPhotoSize",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "maxVideoSize",
"columnName": "maxVideoSize",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "albumLimit",
"columnName": "albumLimit",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "videoEnabled",
"columnName": "videoEnabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pixelfed",
"columnName": "pixelfed",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"uri"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "users",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `instance_uri` TEXT NOT NULL, `username` TEXT NOT NULL, `display_name` TEXT NOT NULL, `avatar_static` TEXT NOT NULL, `isActive` INTEGER NOT NULL, `accessToken` TEXT NOT NULL, `refreshToken` TEXT, `clientId` TEXT NOT NULL, `clientSecret` TEXT NOT NULL, PRIMARY KEY(`user_id`, `instance_uri`), FOREIGN KEY(`instance_uri`) REFERENCES `instances`(`uri`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "user_id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instance_uri",
"columnName": "instance_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "display_name",
"columnName": "display_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "avatar_static",
"columnName": "avatar_static",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isActive",
"columnName": "isActive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accessToken",
"columnName": "accessToken",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "refreshToken",
"columnName": "refreshToken",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "clientId",
"columnName": "clientId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "clientSecret",
"columnName": "clientSecret",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"user_id",
"instance_uri"
]
},
"indices": [
{
"name": "index_users_instance_uri",
"unique": false,
"columnNames": [
"instance_uri"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_users_instance_uri` ON `${TABLE_NAME}` (`instance_uri`)"
}
],
"foreignKeys": [
{
"table": "instances",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"instance_uri"
],
"referencedColumns": [
"uri"
]
}
]
},
{
"tableName": "homePosts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `instance_uri` TEXT NOT NULL, `id` TEXT NOT NULL, `uri` TEXT, `created_at` TEXT, `account` TEXT, `content` TEXT, `visibility` TEXT, `sensitive` INTEGER, `spoiler_text` TEXT, `media_attachments` TEXT, `application` TEXT, `mentions` TEXT, `tags` TEXT, `emojis` TEXT, `reblogs_count` INTEGER, `favourites_count` INTEGER, `replies_count` INTEGER, `url` TEXT, `in_reply_to_id` TEXT, `in_reply_to_account` TEXT, `reblog` TEXT, `poll` TEXT, `card` TEXT, `language` TEXT, `text` TEXT, `favourited` INTEGER, `reblogged` INTEGER, `muted` INTEGER, `bookmarked` INTEGER, `pinned` INTEGER, PRIMARY KEY(`id`, `user_id`, `instance_uri`), FOREIGN KEY(`user_id`, `instance_uri`) REFERENCES `users`(`user_id`, `instance_uri`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "user_id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instance_uri",
"columnName": "instance_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "created_at",
"columnName": "created_at",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "account",
"columnName": "account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "visibility",
"columnName": "visibility",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sensitive",
"columnName": "sensitive",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "spoiler_text",
"columnName": "spoiler_text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "media_attachments",
"columnName": "media_attachments",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "application",
"columnName": "application",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mentions",
"columnName": "mentions",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "emojis",
"columnName": "emojis",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "reblogs_count",
"columnName": "reblogs_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "favourites_count",
"columnName": "favourites_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "replies_count",
"columnName": "replies_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "in_reply_to_id",
"columnName": "in_reply_to_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "in_reply_to_account",
"columnName": "in_reply_to_account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "reblog",
"columnName": "reblog",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "poll",
"columnName": "poll",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "card",
"columnName": "card",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "language",
"columnName": "language",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "favourited",
"columnName": "favourited",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "reblogged",
"columnName": "reblogged",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "muted",
"columnName": "muted",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bookmarked",
"columnName": "bookmarked",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "pinned",
"columnName": "pinned",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"user_id",
"instance_uri"
]
},
"indices": [
{
"name": "index_homePosts_user_id_instance_uri",
"unique": false,
"columnNames": [
"user_id",
"instance_uri"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_homePosts_user_id_instance_uri` ON `${TABLE_NAME}` (`user_id`, `instance_uri`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"user_id",
"instance_uri"
],
"referencedColumns": [
"user_id",
"instance_uri"
]
}
]
},
{
"tableName": "publicPosts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `instance_uri` TEXT NOT NULL, `id` TEXT NOT NULL, `uri` TEXT, `created_at` TEXT, `account` TEXT, `content` TEXT, `visibility` TEXT, `sensitive` INTEGER, `spoiler_text` TEXT, `media_attachments` TEXT, `application` TEXT, `mentions` TEXT, `tags` TEXT, `emojis` TEXT, `reblogs_count` INTEGER, `favourites_count` INTEGER, `replies_count` INTEGER, `url` TEXT, `in_reply_to_id` TEXT, `in_reply_to_account` TEXT, `reblog` TEXT, `poll` TEXT, `card` TEXT, `language` TEXT, `text` TEXT, `favourited` INTEGER, `reblogged` INTEGER, `muted` INTEGER, `bookmarked` INTEGER, `pinned` INTEGER, PRIMARY KEY(`id`, `user_id`, `instance_uri`), FOREIGN KEY(`user_id`, `instance_uri`) REFERENCES `users`(`user_id`, `instance_uri`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "user_id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instance_uri",
"columnName": "instance_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "created_at",
"columnName": "created_at",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "account",
"columnName": "account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "visibility",
"columnName": "visibility",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sensitive",
"columnName": "sensitive",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "spoiler_text",
"columnName": "spoiler_text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "media_attachments",
"columnName": "media_attachments",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "application",
"columnName": "application",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mentions",
"columnName": "mentions",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "emojis",
"columnName": "emojis",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "reblogs_count",
"columnName": "reblogs_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "favourites_count",
"columnName": "favourites_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "replies_count",
"columnName": "replies_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "in_reply_to_id",
"columnName": "in_reply_to_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "in_reply_to_account",
"columnName": "in_reply_to_account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "reblog",
"columnName": "reblog",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "poll",
"columnName": "poll",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "card",
"columnName": "card",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "language",
"columnName": "language",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "favourited",
"columnName": "favourited",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "reblogged",
"columnName": "reblogged",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "muted",
"columnName": "muted",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bookmarked",
"columnName": "bookmarked",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "pinned",
"columnName": "pinned",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"user_id",
"instance_uri"
]
},
"indices": [
{
"name": "index_publicPosts_user_id_instance_uri",
"unique": false,
"columnNames": [
"user_id",
"instance_uri"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_publicPosts_user_id_instance_uri` ON `${TABLE_NAME}` (`user_id`, `instance_uri`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"user_id",
"instance_uri"
],
"referencedColumns": [
"user_id",
"instance_uri"
]
}
]
},
{
"tableName": "notifications",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` TEXT, `created_at` TEXT, `account` TEXT, `status` TEXT, `user_id` TEXT NOT NULL, `instance_uri` TEXT NOT NULL, PRIMARY KEY(`id`, `user_id`, `instance_uri`), FOREIGN KEY(`user_id`, `instance_uri`) REFERENCES `users`(`user_id`, `instance_uri`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "created_at",
"columnName": "created_at",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "account",
"columnName": "account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "user_id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instance_uri",
"columnName": "instance_uri",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"user_id",
"instance_uri"
]
},
"indices": [
{
"name": "index_notifications_user_id_instance_uri",
"unique": false,
"columnNames": [
"user_id",
"instance_uri"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_notifications_user_id_instance_uri` ON `${TABLE_NAME}` (`user_id`, `instance_uri`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"user_id",
"instance_uri"
],
"referencedColumns": [
"user_id",
"instance_uri"
]
}
]
},
{
"tableName": "tabsChecked",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`index` INTEGER NOT NULL, `user_id` TEXT NOT NULL, `instance_uri` TEXT NOT NULL, `tab` TEXT NOT NULL, `checked` INTEGER NOT NULL, PRIMARY KEY(`index`, `user_id`, `instance_uri`), FOREIGN KEY(`user_id`, `instance_uri`) REFERENCES `users`(`user_id`, `instance_uri`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "index",
"columnName": "index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "user_id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instance_uri",
"columnName": "instance_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tab",
"columnName": "tab",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "checked",
"columnName": "checked",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"index",
"user_id",
"instance_uri"
]
},
"indices": [
{
"name": "index_tabsChecked_user_id_instance_uri",
"unique": false,
"columnNames": [
"user_id",
"instance_uri"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_tabsChecked_user_id_instance_uri` ON `${TABLE_NAME}` (`user_id`, `instance_uri`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"user_id",
"instance_uri"
],
"referencedColumns": [
"user_id",
"instance_uri"
]
}
]
}
],
"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, 'cd0310b10eff0e3e961b3cd7a8172b81')"
]
}
}

View File

@ -9,6 +9,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
@ -23,6 +24,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.GravityCompat
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.view.children
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.core.view.marginEnd
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
@ -37,6 +41,7 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.bumptech.glide.Glide
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.color.DynamicColors
import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.navigation.NavigationView
@ -67,12 +72,15 @@ import org.pixeldroid.app.profile.ProfileActivity
import org.pixeldroid.app.searchDiscover.SearchDiscoverFragment
import org.pixeldroid.app.settings.SettingsActivity
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.Tab
import org.pixeldroid.app.utils.api.objects.Notification
import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.db.updateUserInfoDb
import org.pixeldroid.app.utils.hasInternet
import org.pixeldroid.app.utils.loadDefaultMenuTabs
import org.pixeldroid.app.utils.loadDbMenuTabs
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.INSTANCE_NOTIFICATION_TAG
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.SHOW_NOTIFICATION_TAG
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.USER_NOTIFICATION_TAG
@ -95,7 +103,6 @@ class MainActivity : BaseActivity() {
private lateinit var binding: ActivityMainBinding
@OptIn(ExperimentalPagingApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen().setOnExitAnimationListener {
it.remove()
@ -121,24 +128,8 @@ class MainActivity : BaseActivity() {
sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this)
setupDrawer()
val tabs: List<() -> Fragment> = listOf(
{
PostFeedFragment<HomeStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", true) }
}
},
{ SearchDiscoverFragment() },
{ CameraFragment() },
{ NotificationsFragment() },
{
PostFeedFragment<PublicFeedStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", false) }
}
}
)
setupTabs(tabs)
setupTabs()
val showNotification: Boolean = intent.getBooleanExtra(SHOW_NOTIFICATION_TAG, false)
@ -465,29 +456,90 @@ class MainActivity : BaseActivity() {
}
}
private fun setupTabs(tab_array: List<() -> Fragment>){
@OptIn(ExperimentalPagingApi::class)
private fun setupTabs() {
val tabsCheckedDbEntry = with (db.userDao().getActiveUser()!!) {
db.tabsDao().getTabsChecked(user_id, instance_uri)
}
val pageIds = listOf(R.id.page_1, R.id.page_2, R.id.page_3, R.id.page_4, R.id.page_5)
fun Tab.getFragment(): (() -> Fragment) {
return when (this) {
Tab.HOME_FEED -> { {
PostFeedFragment<HomeStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", true) }
}
} }
Tab.SEARCH_DISCOVER_FEED -> { { SearchDiscoverFragment() } }
Tab.CREATE_FEED -> { { CameraFragment() } }
Tab.NOTIFICATIONS_FEED -> { { NotificationsFragment() } }
Tab.PUBLIC_FEED -> { {
PostFeedFragment<PublicFeedStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", false) }
}
} }
}
}
val tabs = if (tabsCheckedDbEntry.isEmpty()) {
// Load default menu
loadDefaultMenuTabs(applicationContext, binding.root)
} else {
// Get current menu visibility and order from settings
val tabsChecked = loadDbMenuTabs(applicationContext, tabsCheckedDbEntry).filter { it.second }.map { it.first }
val bottomNavigationMenu: Menu? = (binding.tabs as? NavigationBarView)?.menu?.apply {
clear()
}
?: binding.navigation?.menu?.apply {
removeGroup(R.id.tabsId)
}
tabsChecked.zip(pageIds).forEach { (tabId, pageId) ->
with(bottomNavigationMenu?.add(R.id.tabsId, pageId, 1, tabId.toLanguageString(baseContext))) {
val tabIcon = tabId.getDrawable(applicationContext)
if (tabIcon != null) {
this?.icon = tabIcon
}
}
}
tabsChecked
}
val tabArray: List<() -> Fragment> = tabs.map { it.getFragment() }
binding.viewPager.reduceDragSensitivity()
binding.viewPager.adapter = object : FragmentStateAdapter(this) {
override fun createFragment(position: Int): Fragment {
return tab_array[position]()
return tabArray[position]()
}
override fun getItemCount(): Int {
return tab_array.size
return tabArray.size
}
}
val notificationId = tabs.zip(pageIds).find {
it.first == Tab.NOTIFICATIONS_FEED
}?.second
fun doAtPageId(pageId: Int): Int {
if (notificationId != null && pageId == notificationId) {
setNotificationBadge(false)
}
return pageId
}
binding.viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
val selected = when(position){
0 -> R.id.page_1
1 -> R.id.page_2
2 -> R.id.page_3
3 -> {
setNotificationBadge(false)
R.id.page_4
}
4 -> R.id.page_5
0 -> doAtPageId(R.id.page_1)
1 -> doAtPageId(R.id.page_2)
2 -> doAtPageId(R.id.page_3)
3 -> doAtPageId(R.id.page_4)
4 -> doAtPageId(R.id.page_5)
else -> null
}
if (selected != null) {

View File

@ -0,0 +1,142 @@
package org.pixeldroid.app.settings
import android.annotation.SuppressLint
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.button.MaterialButton
import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.pixeldroid.app.R
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.entities.TabsDatabaseEntity
import javax.inject.Inject
@AndroidEntryPoint
class ArrangeTabsFragment: DialogFragment() {
@Inject
lateinit var db: AppDatabase
private val model: ArrangeTabsViewModel by viewModels { ArrangeTabsViewModelFactory(requireContext(), db) }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val inflater: LayoutInflater = requireActivity().layoutInflater
val dialogView: View = inflater.inflate(R.layout.layout_tabs_arrange, null)
model.initTabsChecked(dialogView)
val listFeed: RecyclerView = dialogView.findViewById(R.id.tabs)
val listAdapter = ListViewAdapter(model)
listFeed.adapter = listAdapter
listFeed.layoutManager = LinearLayoutManager(requireActivity())
val callback = object: ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) {
override fun onMove(
recyclerView: RecyclerView,
source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
listAdapter.onItemMove(source.bindingAdapterPosition, target.bindingAdapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// Do nothing, all items should remain in the list
}
}
val itemTouchHelper = ItemTouchHelper(callback)
itemTouchHelper.attachToRecyclerView(listFeed)
val dialog = MaterialAlertDialogBuilder(requireContext()).apply {
setIcon(R.drawable.outline_bottom_navigation)
setTitle(R.string.arrange_tabs_summary)
setView(dialogView)
setNegativeButton(android.R.string.cancel) { _, _ -> }
setPositiveButton(android.R.string.ok) { _, _ ->
// Save values into preferences
val tabsChecked = listAdapter.model.uiState.value.tabsChecked.toList()
val tabsDbEntity = tabsChecked.mapIndexed { index, (tab, checked) -> with (db.userDao().getActiveUser()!!) {
TabsDatabaseEntity(index, user_id, instance_uri, tab.name, checked)
} }
lifecycleScope.launch {
db.tabsDao().clearAndRefill(tabsDbEntity, model.uiState.value.userId, model.uiState.value.instanceUri)
}
}
}.create()
return dialog
}
inner class ListViewAdapter(val model: ArrangeTabsViewModel):
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = FrameLayout.inflate(context, R.layout.layout_tab, null)
// Make sure the layout occupies full width
view.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
return object: RecyclerView.ViewHolder(view) {}
}
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val textView: MaterialButton = holder.itemView.findViewById(R.id.textView)
val checkBox: MaterialCheckBox = holder.itemView.findViewById(R.id.checkBox)
val dragHandle: ImageView = holder.itemView.findViewById(R.id.dragHandle)
// Set content of each entry
textView.text = model.uiState.value.tabsChecked[position].first.toLanguageString(requireContext())
checkBox.isChecked = model.uiState.value.tabsChecked[position].second
// Also interact with checkbox when button is clicked
textView.setOnClickListener {
val isCheckedNew = !model.uiState.value.tabsChecked[position].second
model.tabsCheckReplace(position, Pair(model.uiState.value.tabsChecked[position].first, isCheckedNew))
checkBox.isChecked = isCheckedNew
// Disable OK button when no tab is selected or when strictly more than 5 tabs are selected
val maxItemCount = BottomNavigationView(requireContext()).maxItemCount // = 5
(requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled =
with (model.uiState.value.tabsChecked.count { (_, v) -> v }) { this in 1..maxItemCount}
}
// Also highlight button when checkbox is clicked
checkBox.setOnTouchListener { _, motionEvent ->
textView.dispatchTouchEvent(motionEvent)
}
// Do not highlight the button when the drag handle is touched
dragHandle.setOnTouchListener { _, _ -> true }
}
override fun getItemCount(): Int {
return model.uiState.value.tabsChecked.size
}
fun onItemMove(from: Int, to: Int) {
val previous = model.tabsCheckedRemove(from)
model.tabsCheckedAdd(to, previous)
notifyItemMoved(from, to)
notifyItemChanged(to) // necessary to avoid checkBox issues
}
}
}

View File

@ -0,0 +1,114 @@
package org.pixeldroid.app.settings
import android.content.Context
import android.view.View
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import org.pixeldroid.app.utils.Tab
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.entities.TabsDatabaseEntity
import org.pixeldroid.app.utils.loadDbMenuTabs
import org.pixeldroid.app.utils.loadDefaultMenuTabs
class ArrangeTabsViewModelFactory(
private val context: Context, private val db: AppDatabase
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ArrangeTabsViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return ArrangeTabsViewModel(context, db) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
class ArrangeTabsViewModel(
private val fragmentContext: Context,
private val db: AppDatabase
): ViewModel() {
private val _uiState = MutableStateFlow(ArrangeTabsUiState())
val uiState: StateFlow<ArrangeTabsUiState> = _uiState
private var oldTabsChecked: MutableList<Pair<Tab, Boolean>> = mutableListOf()
init {
initTabsDbEntities()
}
private fun initTabsDbEntities() {
val user = db.userDao().getActiveUser()!!
_uiState.update { currentUiState ->
currentUiState.copy(
userId = user.user_id,
instanceUri = user.instance_uri,
)
}
_uiState.update { currentUiState ->
currentUiState.copy(
tabsDbEntities = db.tabsDao().getTabsChecked(_uiState.value.userId, _uiState.value.instanceUri)
)
}
}
fun initTabsChecked(view: View) {
if (oldTabsChecked.isEmpty()) {
// Only load tabsChecked if the model has not been updated
_uiState.update { currentUiState ->
currentUiState.copy(
tabsChecked = if (_uiState.value.tabsDbEntities.isEmpty()) {
// Load default menu
val list = loadDefaultMenuTabs(fragmentContext, view)
list.zip(List(list.size){true}.toTypedArray()).toList()
} else {
// Get current menu visibility and order from settings
loadDbMenuTabs(fragmentContext, _uiState.value.tabsDbEntities).toList()
}
)
}
}
}
fun tabsCheckReplace(position: Int, pair: Pair<Tab, Boolean>) {
oldTabsChecked = _uiState.value.tabsChecked.toMutableList()
oldTabsChecked[position] = pair
_uiState.update { currentUiState ->
currentUiState.copy(
tabsChecked = oldTabsChecked.toList()
)
}
}
fun tabsCheckedRemove(position: Int): Pair<Tab, Boolean> {
oldTabsChecked = _uiState.value.tabsChecked.toMutableList()
val removedPair = oldTabsChecked.removeAt(position)
_uiState.update { currentUiState ->
currentUiState.copy(
tabsChecked = oldTabsChecked.toList()
)
}
return removedPair
}
fun tabsCheckedAdd(position: Int, pair: Pair<Tab, Boolean>) {
oldTabsChecked = _uiState.value.tabsChecked.toMutableList()
oldTabsChecked.add(position, pair)
_uiState.update { currentUiState ->
currentUiState.copy(
tabsChecked = oldTabsChecked.toList()
)
}
}
}
data class ArrangeTabsUiState(
val userId: String = "",
val instanceUri: String = "",
val tabsDbEntities: List<TabsDatabaseEntity> = listOf(),
val tabsChecked: List<Pair<Tab, Boolean>> = listOf()
)

View File

@ -0,0 +1,56 @@
package org.pixeldroid.app.settings
import android.app.Dialog
import android.content.res.XmlResourceParser
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.pixeldroid.app.R
class LanguageSettingFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val list: MutableList<String> = mutableListOf()
// IDE doesn't find it, but compiling works apparently?
resources.getXml(R.xml._generated_res_locale_config).use {
var eventType = it.eventType
while (eventType != XmlResourceParser.END_DOCUMENT) {
when (eventType) {
XmlResourceParser.START_TAG -> {
if (it.name == "locale") {
list.add(it.getAttributeValue(0))
}
}
}
eventType = it.next()
}
}
val locales = AppCompatDelegate.getApplicationLocales()
val checkedItem: Int =
if(locales.isEmpty) 0
else {
// For some reason we get a bit inconsistent language tags. This normalises it for
// the currently used languages, but it might break in the future if we add some
val index = list.indexOf(locales.get(0)?.toLanguageTag()?.lowercase()?.replace('_', '-'))
// If found, we want to compensate for the first in the list being the default
if(index == -1) -1
else index + 1
}
return MaterialAlertDialogBuilder(requireContext()).apply {
setIcon(R.drawable.translate_black_24dp)
setTitle(R.string.language)
setSingleChoiceItems((mutableListOf(getString(R.string.default_system)) + list.map {
val appLocale = LocaleListCompat.forLanguageTags(it)
appLocale.get(0)!!.getDisplayName(appLocale.get(0)!!)
}).toTypedArray(), checkedItem) { dialog, which ->
val languageTag = if(which in 1..list.size) list[which - 1] else null
dialog.dismiss()
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(languageTag))
}
setNegativeButton(android.R.string.ok) { _, _ -> }
}.create()
}
}

View File

@ -1,28 +1,27 @@
package org.pixeldroid.app.settings
import android.app.Dialog
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.XmlResourceParser
import android.os.Build
import android.os.Bundle
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.fragment.app.DialogFragment
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.pixeldroid.app.main.MainActivity
import dagger.hilt.android.AndroidEntryPoint
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.SettingsBinding
import org.pixeldroid.common.ThemedActivity
import org.pixeldroid.app.main.MainActivity
import org.pixeldroid.app.utils.setThemeFromPreferences
import org.pixeldroid.common.ThemedActivity
@AndroidEntryPoint
class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
private var restartMainOnExit = false
override fun onCreate(savedInstanceState: Bundle?) {
@ -103,6 +102,8 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC
dialogFragment = ColorPreferenceDialog((preference as ColorPreference?)!!)
} else if(preference.key == "language"){
dialogFragment = LanguageSettingFragment()
} else if (preference.key == "arrange_tabs") {
dialogFragment = ArrangeTabsFragment()
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0)
@ -129,49 +130,4 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC
}
}
}
}
class LanguageSettingFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val list: MutableList<String> = mutableListOf()
// IDE doesn't find it, but compiling works apparently?
resources.getXml(R.xml._generated_res_locale_config).use {
var eventType = it.eventType
while (eventType != XmlResourceParser.END_DOCUMENT) {
when (eventType) {
XmlResourceParser.START_TAG -> {
if (it.name == "locale") {
list.add(it.getAttributeValue(0))
}
}
}
eventType = it.next()
}
}
val locales = AppCompatDelegate.getApplicationLocales()
val checkedItem: Int =
if(locales.isEmpty) 0
else {
// For some reason we get a bit inconsistent language tags. This normalises it for
// the currently used languages, but it might break in the future if we add some
val index = list.indexOf(locales.get(0)?.toLanguageTag()?.lowercase()?.replace('_', '-'))
// If found, we want to compensate for the first in the list being the default
if(index == -1) -1
else index + 1
}
return MaterialAlertDialogBuilder(requireContext()).apply {
setIcon(R.drawable.translate_black_24dp)
setTitle(R.string.language)
setSingleChoiceItems((mutableListOf(getString(R.string.default_system)) + list.map {
val appLocale = LocaleListCompat.forLanguageTags(it)
appLocale.get(0)!!.getDisplayName(appLocale.get(0)!!)
}).toTypedArray(), checkedItem) { dialog, which ->
val languageTag = if(which in 1..list.size) list[which - 1] else null
dialog.dismiss()
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(languageTag))
}
setNegativeButton(android.R.string.ok) { _, _ -> }
}.create()
}
}
}

View File

@ -7,15 +7,19 @@ import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
import android.net.Uri
import android.os.Build
import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager
import android.webkit.MimeTypeMap
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.PopupMenu
import androidx.browser.customtabs.CustomTabsIntent
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
@ -29,6 +33,7 @@ import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializer
import okhttp3.HttpUrl
import org.pixeldroid.app.R
import org.pixeldroid.app.utils.db.entities.TabsDatabaseEntity
import java.time.Instant
import java.time.format.DateTimeFormatter
import java.util.Locale
@ -192,4 +197,67 @@ fun <T> Fragment.bindingLifecycleAware(): ReadWriteProperty<Fragment, T> =
binding = value
this@bindingLifecycleAware.viewLifecycleOwner.lifecycle.addObserver(this)
}
}
}
fun loadDefaultMenuTabs(context: Context, anchor: View): List<Tab> {
return with(PopupMenu(context, anchor)) {
val menu = this.menu
menuInflater.inflate(R.menu.navigation_main, menu)
menu.removeGroup(R.id.bottomNavigationGroup)
(0 until menu.size()).map { Tab.fromLanguageString(context, menu.getItem(it).title.toString()) }
}
}
fun loadDbMenuTabs(ctx: Context, tabsDbEntry: List<TabsDatabaseEntity>): List<Pair<Tab, Boolean>> {
return tabsDbEntry.map {
Pair(Tab.fromName(it.tab), it.checked)
}
}
enum class Tab {
HOME_FEED, SEARCH_DISCOVER_FEED, CREATE_FEED, NOTIFICATIONS_FEED, PUBLIC_FEED;
fun toLanguageString(ctx: Context): String {
return ctx.getString(
when (this) {
HOME_FEED -> R.string.home_feed
SEARCH_DISCOVER_FEED -> R.string.search_discover_feed
CREATE_FEED -> R.string.create_feed
NOTIFICATIONS_FEED -> R.string.notifications_feed
PUBLIC_FEED -> R.string.public_feed
}
)
}
fun toName(): String {
return this.name
}
fun getDrawable(ctx: Context): Drawable? {
val resId = when (this) {
HOME_FEED -> R.drawable.selector_home_feed
SEARCH_DISCOVER_FEED -> R.drawable.ic_search_white_24dp
CREATE_FEED -> R.drawable.selector_camera
NOTIFICATIONS_FEED -> R.drawable.selector_notifications
PUBLIC_FEED -> R.drawable.ic_filter_black_24dp
}
return AppCompatResources.getDrawable(ctx, resId)
}
companion object {
fun fromLanguageString(ctx: Context, name: String): Tab {
return when (name) {
ctx.getString(R.string.home_feed) -> HOME_FEED
ctx.getString(R.string.search_discover_feed) -> SEARCH_DISCOVER_FEED
ctx.getString(R.string.create_feed) -> CREATE_FEED
ctx.getString(R.string.notifications_feed) -> NOTIFICATIONS_FEED
ctx.getString(R.string.public_feed) -> PUBLIC_FEED
else -> HOME_FEED
}
}
fun fromName(name: String): Tab {
return entries.filter { it.name == name }.getOrElse(0) { HOME_FEED }
}
}
}

View File

@ -1,5 +1,6 @@
package org.pixeldroid.app.utils.db
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
@ -14,15 +15,20 @@ import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.api.objects.Notification
import org.pixeldroid.app.utils.db.entities.TabsDatabaseEntity
@Database(entities = [
InstanceDatabaseEntity::class,
UserDatabaseEntity::class,
HomeStatusDatabaseEntity::class,
PublicFeedStatusDatabaseEntity::class,
Notification::class
Notification::class,
TabsDatabaseEntity::class
],
version = 7,
autoMigrations = [
AutoMigration(from = 6, to = 7)
],
version = 6
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
@ -31,6 +37,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun homePostDao(): HomePostDao
abstract fun publicPostDao(): PublicPostDao
abstract fun notificationDao(): NotificationDao
abstract fun tabsDao(): TabsDao
}
val MIGRATION_3_4 = object : Migration(3, 4) {
@ -49,4 +56,4 @@ val MIGRATION_5_6 = object : Migration(5, 6) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE instances ADD COLUMN pixelfed INTEGER NOT NULL DEFAULT 1")
}
}
}

View File

@ -0,0 +1,46 @@
package org.pixeldroid.app.utils.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import org.pixeldroid.app.utils.db.entities.TabsDatabaseEntity
@Dao
interface TabsDao {
@Query("SELECT * FROM tabsChecked WHERE `index`=:index AND `user_id`=:userId AND `instance_uri`=:instanceUri")
fun getTabChecked(index: Int, userId: String, instanceUri: String): TabsDatabaseEntity
@Query("SELECT * FROM tabsChecked WHERE `user_id`=:userId AND `instance_uri`=:instanceUri")
fun getTabsChecked(userId: String, instanceUri: String): List<TabsDatabaseEntity>
@Query("DELETE FROM tabsChecked WHERE `index`=:index AND `user_id`=:userId AND `instance_uri`=:instanceUri")
fun deleteTabChecked(index: Int, userId: String, instanceUri: String)
@Query("DELETE FROM tabsChecked WHERE `user_id`=:userId AND `instance_uri`=:instanceUri")
fun deleteTabsChecked(userId: String, instanceUri: String)
/**
* Insert a tab, if it already exists return -1
*/
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertTabChecked(tabChecked: TabsDatabaseEntity): Long
@Update
suspend fun updateTabChecked(tabChecked: TabsDatabaseEntity)
@Transaction
suspend fun insertOrUpdate(tabChecked: TabsDatabaseEntity) {
if (insertTabChecked(tabChecked) == -1L) {
updateTabChecked(tabChecked)
}
}
@Transaction
suspend fun clearAndRefill(tabsChecked: List<TabsDatabaseEntity>, userId: String, instanceUri: String) {
deleteTabsChecked(userId, instanceUri)
tabsChecked.forEach { insertTabChecked(it) }
}
}

View File

@ -0,0 +1,25 @@
package org.pixeldroid.app.utils.db.entities
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
@Entity(
tableName = "tabsChecked",
primaryKeys = ["index", "user_id", "instance_uri"],
foreignKeys = [ForeignKey(
entity = UserDatabaseEntity::class,
parentColumns = arrayOf("user_id", "instance_uri"),
childColumns = arrayOf("user_id", "instance_uri"),
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
)],
indices = [Index(value = ["user_id", "instance_uri"])]
)
data class TabsDatabaseEntity(
var index: Int,
var user_id: String,
var instance_uri: String,
var tab: String,
var checked: Boolean = true,
)

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorOnBackground" android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,600L760,600L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,600ZM200,680L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,680L200,680ZM200,680L200,680L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,680Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorOnBackground" android:pathData="M346,820L100,574Q90,564 85,552Q80,540 80,527Q80,514 85,502Q90,490 100,480L330,251L255,176Q242,163 241.5,145Q241,127 254,113Q267,99 286,99Q305,99 319,113L686,480Q696,490 700.5,502Q705,514 705,527Q705,540 700.5,552Q696,564 686,574L440,820Q430,830 418,835Q406,840 393,840Q380,840 368,835Q356,830 346,820ZM393,314L179,528Q179,528 179,528Q179,528 179,528L607,528Q607,528 607,528Q607,528 607,528L393,314ZM792,840Q756,840 731,814.5Q706,789 706,752Q706,725 719.5,701Q733,677 750,654L769,630Q778,619 792.5,618.5Q807,618 816,629L836,654Q852,677 866,701Q880,725 880,752Q880,789 854,814.5Q828,840 792,840Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorOnBackground" android:pathData="M200,600Q183,600 171.5,588.5Q160,577 160,560Q160,543 171.5,531.5Q183,520 200,520L760,520Q777,520 788.5,531.5Q800,543 800,560Q800,577 788.5,588.5Q777,600 760,600L200,600ZM200,440Q183,440 171.5,428.5Q160,417 160,400Q160,383 171.5,371.5Q183,360 200,360L760,360Q777,360 788.5,371.5Q800,383 800,400Q800,417 788.5,428.5Q777,440 760,440L200,440Z"/>
</vector>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cornerRadius="0dp"
android:gravity="start|center_vertical"
android:textColor="?attr/colorOnBackground"
android:textAllCaps="false"
android:textAppearance="?attr/textAppearanceBody1"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingStart="75dp"
android:paddingEnd="75dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="40dp"
app:layout_constraintBottom_toBottomOf="@id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/textView"
app:layout_constraintVertical_bias="0.5"
tools:ignore="RtlSymmetry" />
<ImageView
android:id="@+id/dragHandle"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:src="@drawable/rounded_drag_handle"
android:paddingEnd="15dp"
android:paddingStart="15dp"
android:layout_marginEnd="15dp"
app:layout_constraintBottom_toBottomOf="@id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/textView"
app:layout_constraintVertical_bias="0.5"
tools:ignore="RtlSymmetry" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.widget.Space
android:id="@+id/titleDividerNoCustom"
android:layout_width="match_parent"
android:layout_height="@dimen/m3_alert_dialog_title_bottom_margin"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -27,7 +27,9 @@
android:icon="@drawable/ic_filter_black_24dp"
android:title="@string/public_feed"/>
</group>
<group
android:id="@+id/bottomNavigationGroup"
android:orderInCategory="3">
<item
android:id="@+id/my_profile"
android:enabled="true"
@ -43,4 +45,6 @@
android:enabled="true"
android:icon="@drawable/logout"
android:title="@string/logout"/>
</group>
</menu>

View File

@ -348,4 +348,7 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
<string name="continue_post_creation">Continue</string>
<string name="extraneous_pictures_stories">Pictures after the first were removed but can be restored by switching back to creating a Post</string>
<string name="story_duration">Story Duration</string>
<string name="arrange_tabs_summary">Arrange tabs</string>
<string name="arrange_tabs_description">Change visibility and order of tabs</string>
<string name="content_header">Content</string>
</resources>

View File

@ -17,54 +17,64 @@
android:title="@string/accentColorTitle"
android:key="themeColor"
android:defaultValue="0"
android:summary="@string/accentColorSummary" />
android:summary="@string/accentColorSummary"
app:icon="@drawable/rounded_colors"/>
<ListPreference
app:key="language"
app:title="@string/language"
app:icon="@drawable/translate_black_24dp" />
</PreferenceCategory>
<ListPreference
app:key="language"
app:title="@string/language"
app:icon="@drawable/translate_black_24dp" />
<PreferenceCategory app:title="@string/content_header">
<ListPreference
android:key="arrange_tabs"
android:title="@string/arrange_tabs_summary"
android:summary="@string/arrange_tabs_description"
android:icon="@drawable/outline_bottom_navigation" />
<CheckBoxPreference app:key="always_show_nsfw" app:title="@string/always_show_nsfw"
app:icon="@drawable/eye_black_24dp" android:defaultValue="false"
android:summary="@string/summary_always_show_nsfw"/>
<CheckBoxPreference app:key="always_show_nsfw" app:title="@string/always_show_nsfw"
app:icon="@drawable/eye_black_24dp" android:defaultValue="false"
android:summary="@string/summary_always_show_nsfw"/>
<Preference android:title="@string/notifications_settings"
android:key="notification"
android:summary="@string/notifications_settings_summary"
app:icon="@drawable/ic_baseline_notifications_active_24">
<intent android:action="android.settings.APP_NOTIFICATION_SETTINGS">
<extra android:name="android.provider.extra.APP_PACKAGE"
android:value="@string/application_id" />
</intent>
</Preference>
<EditTextPreference android:title="@string/description_template"
android:key="prefill_description"
android:summary="@string/description_template_summary"
app:icon="@drawable/note" />
<EditTextPreference android:title="@string/description_template"
android:key="prefill_description"
android:summary="@string/description_template_summary"
app:icon="@drawable/note" />
<Preference android:title="@string/notifications_settings"
android:key="notification"
android:summary="@string/notifications_settings_summary"
app:icon="@drawable/ic_baseline_notifications_active_24">
<intent android:action="android.settings.APP_NOTIFICATION_SETTINGS">
<extra android:name="android.provider.extra.APP_PACKAGE"
android:value="@string/application_id" />
</intent>
</Preference>
</PreferenceCategory>
<Preference android:title="@string/about"
android:key="about"
android:summary="@string/about_pixeldroid"
app:icon="@drawable/info_black_24dp">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="@string/application_id"
android:targetClass="org.pixeldroid.common.AboutActivity">
<extra android:name="buildVersion" android:value="@string/versionName" />
<extra android:name="appImage" android:value="mascot" />
<extra android:name="appImageWidth" android:value="508" />
<extra android:name="appImageTopMargin" android:value="-130" />
<extra android:name="appImageBottomMargin" android:value="-130" />
<extra android:name="appImageLeftMargin" android:value="0" />
<extra android:name="appImageRightMargin" android:value="0" />
<extra android:name="appName" android:value="@string/app_name" />
<extra android:name="aboutAppDescription" android:value="@string/license_info" />
<extra android:name="website" android:value="@string/project_website" />
<extra android:name="translatePlatformUrl" android:value="https://weblate.pixeldroid.org" />
<extra android:name="contributeForgeUrl" android:value="https://gitlab.shinice.net/pixeldroid/PixelDroid" />
</intent>
</Preference>
<PreferenceCategory app:title="@string/about">
<Preference android:title="@string/about"
android:key="about"
android:summary="@string/about_pixeldroid"
app:icon="@drawable/info_black_24dp">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="@string/application_id"
android:targetClass="org.pixeldroid.common.AboutActivity">
<extra android:name="buildVersion" android:value="@string/versionName" />
<extra android:name="appImage" android:value="mascot" />
<extra android:name="appImageWidth" android:value="508" />
<extra android:name="appImageTopMargin" android:value="-130" />
<extra android:name="appImageBottomMargin" android:value="-130" />
<extra android:name="appImageLeftMargin" android:value="0" />
<extra android:name="appImageRightMargin" android:value="0" />
<extra android:name="appName" android:value="@string/app_name" />
<extra android:name="aboutAppDescription" android:value="@string/license_info" />
<extra android:name="website" android:value="@string/project_website" />
<extra android:name="translatePlatformUrl" android:value="https://weblate.pixeldroid.org" />
<extra android:name="contributeForgeUrl" android:value="https://gitlab.shinice.net/pixeldroid/PixelDroid" />
</intent>
</Preference>
</PreferenceCategory>
</PreferenceScreen>