Compare commits

...

14 Commits

Author SHA1 Message Date
558828f3e0 🚀 Launch 1.3.6+5 2024-10-07 16:54:29 +08:00
09dc7d2a0d 💄 Brightness of code block 2024-10-07 16:29:36 +08:00
6876d2e7c0 Syntax highlighting in markdown
💄 Optimize content rendering
2024-10-07 16:23:25 +08:00
3a5964730c 🚀 1.3.6+4 2024-10-07 02:12:50 +08:00
271c722df3 🐛 Bug fixes on background image 2024-10-07 01:47:34 +08:00
97656249f2 ⬆️ Upgrade deps 2024-10-06 23:23:11 +08:00
d7e6fe2d8f 💄 More transparency 2024-10-06 23:06:33 +08:00
2e9c4d166e 💄 Optimize designs and bug fixes with background image 2024-10-06 22:38:37 +08:00
c5258cb9ca 🚀 Launch 1.3.6+3 2024-10-06 19:57:17 +08:00
47c535910d 💄 Optimize the style with background image 2024-10-06 19:54:32 +08:00
66f2f33394 🐛 Bug fixes with background image 2024-10-06 19:41:44 +08:00
f5fbe1f483 Better theme & background image 2024-10-06 19:29:47 +08:00
fcf4dc7a2d ♻️ Use unified root container 2024-10-06 17:37:07 +08:00
43b7059957 🐛 Bug fixes and optimization 2024-10-06 17:31:44 +08:00
49 changed files with 2920 additions and 1469 deletions

View File

@ -0,0 +1,531 @@
{
"name": "Dart",
"version": "1.2.3",
"fileTypes": ["dart"],
"scopeName": "source.dart",
"foldingStartMarker": "\\{\\s*$",
"foldingStopMarker": "^\\s*\\}",
"patterns": [
{
"name": "meta.preprocessor.script.dart",
"match": "^(#!.*)$"
},
{
"name": "meta.declaration.dart",
"begin": "^\\w*\\b(library|import|part of|part|export)\\b",
"beginCaptures": {
"0": {
"name": "keyword.other.import.dart"
}
},
"end": ";",
"endCaptures": {
"0": {
"name": "punctuation.terminator.dart"
}
},
"patterns": [
{
"include": "#strings"
},
{
"include": "#comments"
},
{
"name": "keyword.other.import.dart",
"match": "\\b(as|show|hide)\\b"
},
{
"name": "keyword.control.dart",
"match": "\\b(if)\\b"
}
]
},
{
"include": "#comments"
},
{
"include": "#punctuation"
},
{
"include": "#annotations"
},
{
"include": "#keywords"
},
{
"include": "#constants-and-special-vars"
},
{
"include": "#operators"
},
{
"include": "#strings"
}
],
"repository": {
"dartdoc": {
"patterns": [
{
"match": "(\\[.*?\\])",
"captures": {
"0": {
"name": "variable.name.source.dart"
}
}
},
{
"match": "^ {4,}(?![ \\*]).*",
"captures": {
"0": {
"name": "variable.name.source.dart"
}
}
},
{
"contentName": "variable.other.source.dart",
"begin": "```.*?$",
"end": "```"
},
{
"match": "(`.*?`)",
"captures": {
"0": {
"name": "variable.other.source.dart"
}
}
},
{
"match": "(`.*?`)",
"captures": {
"0": {
"name": "variable.other.source.dart"
}
}
},
{
"match": "(\\* (( ).*))$",
"captures": {
"2": {
"name": "variable.other.source.dart"
}
}
}
]
},
"comments": {
"patterns": [
{
"name": "comment.block.empty.dart",
"match": "/\\*\\*/",
"captures": {
"0": {
"name": "punctuation.definition.comment.dart"
}
}
},
{
"include": "#comments-doc-oldschool"
},
{
"include": "#comments-doc"
},
{
"include": "#comments-inline"
}
]
},
"comments-doc-oldschool": {
"patterns": [
{
"name": "comment.block.documentation.dart",
"begin": "/\\*\\*",
"end": "\\*/",
"patterns": [
{
"include": "#comments-doc-oldschool"
},
{
"include": "#comments-block"
},
{
"include": "#dartdoc"
}
]
}
]
},
"comments-doc": {
"patterns": [
{
"name": "comment.block.documentation.dart",
"begin": "///",
"while": "^\\s*///",
"patterns": [
{
"include": "#dartdoc"
}
]
}
]
},
"comments-inline": {
"patterns": [
{
"include": "#comments-block"
},
{
"match": "((//).*)$",
"captures": {
"1": {
"name": "comment.line.double-slash.dart"
}
}
}
]
},
"comments-block": {
"patterns": [
{
"name": "comment.block.dart",
"begin": "/\\*",
"end": "\\*/",
"patterns": [
{
"include": "#comments-block"
}
]
}
]
},
"annotations": {
"patterns": [
{
"name": "storage.type.annotation.dart",
"match": "@[a-zA-Z]+"
}
]
},
"constants-and-special-vars": {
"patterns": [
{
"name": "constant.language.dart",
"match": "(?<!\\$)\\b(true|false|null)\\b(?!\\$)"
},
{
"name": "variable.language.dart",
"match": "(?<!\\$)\\b(this|super)\\b(?!\\$)"
},
{
"name": "constant.numeric.dart",
"match": "(?<!\\$)\\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)\\b(?!\\$)"
},
{
"include": "#class-identifier"
},
{
"include": "#function-identifier"
}
]
},
"class-identifier": {
"patterns": [
{
"match": "(?<!\\$)\\b(bool|num|int|double|dynamic)\\b(?!\\$)",
"name": "support.class.dart"
},
{
"match": "(?<!\\$)\\bvoid\\b(?!\\$)",
"name": "storage.type.primitive.dart"
},
{
"begin": "(?<![a-zA-Z0-9_$])([_$]*[A-Z][a-zA-Z0-9_$]*)\\b",
"end": "(?!<)",
"beginCaptures": {
"1": {
"name": "support.class.dart"
}
},
"patterns": [
{
"include": "#type-args"
}
]
}
]
},
"function-identifier": {
"patterns": [
{
"match": "([_$]*[a-z][a-zA-Z0-9_$]*)(<(?:[a-zA-Z0-9_$<>?]|,\\s*|\\s+extends\\s+)+>)?[!?]?\\(",
"captures": {
"1": {
"name": "entity.name.function.dart"
},
"2": {
"patterns": [
{
"include": "#type-args"
}
]
}
}
}
]
},
"type-args": {
"begin": "(<)",
"end": "(>)",
"beginCaptures": {
"1": {
"name": "other.source.dart"
}
},
"endCaptures": {
"1": {
"name": "other.source.dart"
}
},
"patterns": [
{
"include": "#class-identifier"
},
{
"match": ","
},
{
"name": "keyword.declaration.dart",
"match": "extends"
},
{
"include": "#comments"
}
]
},
"keywords": {
"patterns": [
{
"name": "keyword.cast.dart",
"match": "(?<!\\$)\\bas\\b(?!\\$)"
},
{
"name": "keyword.control.catch-exception.dart",
"match": "(?<!\\$)\\b(try|on|catch|finally|throw|rethrow)\\b(?!\\$)"
},
{
"name": "keyword.control.dart",
"match": "(?<!\\$)\\b(break|case|continue|default|do|else|for|if|in|return|switch|while|when)\\b(?!\\$)"
},
{
"name": "keyword.control.dart",
"match": "(?<!\\$)\\b(sync(\\*)?|async(\\*)?|await|yield(\\*)?)\\b(?!\\$)"
},
{
"name": "keyword.control.dart",
"match": "(?<!\\$)\\bassert\\b(?!\\$)"
},
{
"name": "keyword.control.new.dart",
"match": "(?<!\\$)\\b(new)\\b(?!\\$)"
},
{
"name": "keyword.declaration.dart",
"match": "(?<!\\$)\\b(abstract|sealed|base|interface|class|enum|extends|extension type|extension|external|factory|implements|get(?!\\()|mixin|native|operator|set(?!\\()|typedef|with|covariant)\\b(?!\\$)"
},
{
"name": "storage.modifier.dart",
"match": "(?<!\\$)\\b(static|final|const|required|late)\\b(?!\\$)"
},
{
"name": "storage.type.primitive.dart",
"match": "(?<!\\$)\\b(?:void|var)\\b(?!\\$)"
}
]
},
"operators": {
"patterns": [
{
"name": "keyword.operator.dart",
"match": "(?<!\\$)\\b(is\\!?)\\b(?!\\$)"
},
{
"name": "keyword.operator.ternary.dart",
"match": "\\?|:"
},
{
"name": "keyword.operator.bitwise.dart",
"match": "(<<|>>>?|~|\\^|\\||&)"
},
{
"name": "keyword.operator.assignment.bitwise.dart",
"match": "((&|\\^|\\||<<|>>>?)=)"
},
{
"name": "keyword.operator.closure.dart",
"match": "(=>)"
},
{
"name": "keyword.operator.comparison.dart",
"match": "(==|!=|<=?|>=?)"
},
{
"name": "keyword.operator.assignment.arithmetic.dart",
"match": "(([+*/%-]|\\~)=)"
},
{
"name": "keyword.operator.assignment.dart",
"match": "(=)"
},
{
"name": "keyword.operator.increment-decrement.dart",
"match": "(\\-\\-|\\+\\+)"
},
{
"name": "keyword.operator.arithmetic.dart",
"match": "(\\-|\\+|\\*|\\/|\\~\\/|%)"
},
{
"name": "keyword.operator.logical.dart",
"match": "(!|&&|\\|\\|)"
}
]
},
"string-interp": {
"patterns": [
{
"match": "\\$([a-zA-Z0-9_]+)",
"captures": {
"1": {
"name": "variable.parameter.dart"
}
}
},
{
"name": "string.interpolated.expression.dart",
"begin": "\\$\\{",
"end": "\\}",
"patterns": [
{
"include": "#constants-and-special-vars",
"name": "variable.parameter.dart"
},
{
"include": "#strings"
},
{
"name": "variable.parameter.dart",
"match": "[a-zA-Z0-9_]+"
}
]
},
{
"name": "constant.character.escape.dart",
"match": "\\\\."
}
]
},
"strings": {
"patterns": [
{
"name": "string.interpolated.triple.double.dart",
"begin": "(?<!r)\"\"\"",
"end": "\"\"\"(?!\")",
"patterns": [
{
"include": "#string-interp"
}
]
},
{
"name": "string.interpolated.triple.single.dart",
"begin": "(?<!r)'''",
"end": "'''(?!')",
"patterns": [
{
"include": "#string-interp"
}
]
},
{
"name": "string.quoted.triple.double.dart",
"begin": "r\"\"\"",
"end": "\"\"\"(?!\")"
},
{
"name": "string.quoted.triple.single.dart",
"begin": "r'''",
"end": "'''(?!')"
},
{
"name": "string.interpolated.double.dart",
"begin": "(?<!\\|r)\"",
"end": "\"",
"patterns": [
{
"name": "invalid.string.newline",
"match": "\\n"
},
{
"include": "#string-interp"
}
]
},
{
"name": "string.quoted.double.dart",
"begin": "r\"",
"end": "\"",
"patterns": [
{
"name": "invalid.string.newline",
"match": "\\n"
}
]
},
{
"name": "string.interpolated.single.dart",
"begin": "(?<!\\|r)'",
"end": "'",
"patterns": [
{
"name": "invalid.string.newline",
"match": "\\n"
},
{
"include": "#string-interp"
}
]
},
{
"name": "string.quoted.single.dart",
"begin": "r'",
"end": "'",
"patterns": [
{
"name": "invalid.string.newline",
"match": "\\n"
}
]
}
]
},
"punctuation": {
"patterns": [
{
"name": "punctuation.comma.dart",
"match": ","
},
{
"name": "punctuation.terminator.dart",
"match": ";"
},
{
"name": "punctuation.dot.dart",
"match": "\\."
}
]
}
}
}

View File

@ -0,0 +1,212 @@
{
"fileTypes": ["json"],
"foldingStartMarker": "^\\s*[{\\[](?!.*[}\\]],?\\s*$)|[{\\[]\\s*$",
"foldingStopMarker": "^\\s*[}\\]]",
"keyEquivalent": "^~J",
"name": "JSON (Javascript Next)",
"patterns": [
{
"include": "#value"
}
],
"repository": {
"array": {
"begin": "\\[",
"beginCaptures": {
"0": {
"name": "punctuation.definition.array.begin.json"
}
},
"end": "\\]",
"endCaptures": {
"0": {
"name": "punctuation.definition.array.end.json"
}
},
"name": "meta.structure.array.json",
"patterns": [
{
"include": "#value"
},
{
"match": ",",
"name": "punctuation.separator.array.json"
},
{
"match": "[^\\s\\]]",
"name": "invalid.illegal.expected-array-separator.json"
}
]
},
"comments": {
"patterns": [
{
"begin": "/\\*\\*",
"captures": {
"0": {
"name": "punctuation.definition.comment.json"
}
},
"end": "\\*/",
"name": "comment.block.documentation.json"
},
{
"begin": "/\\*",
"captures": {
"0": {
"name": "punctuation.definition.comment.json"
}
},
"end": "\\*/",
"name": "comment.block.json"
},
{
"captures": {
"1": {
"name": "punctuation.definition.comment.json"
}
},
"match": "(//).*$\\n?",
"name": "comment.line.double-slash.js"
}
]
},
"constant": {
"match": "\\b(?:true|false|null)\\b",
"name": "constant.language.json"
},
"number": {
"match": "-?(?:0|[1-9]\\d*)\n(?:\n(?:\n\\.\\d+)?\n(?:\n[eE][+-]?\\d+)?)?",
"name": "constant.numeric.json"
},
"object": {
"begin": "\\{",
"beginCaptures": {
"0": {
"name": "punctuation.definition.dictionary.begin.json"
}
},
"end": "\\}",
"endCaptures": {
"0": {
"name": "punctuation.definition.dictionary.end.json"
}
},
"name": "meta.structure.dictionary.json",
"patterns": [
{
"comment": "the JSON object key",
"include": "#objectkey"
},
{
"include": "#comments"
},
{
"begin": ":",
"beginCaptures": {
"0": {
"name": "punctuation.separator.dictionary.key-value.json"
}
},
"end": "(,)|(?=\\})",
"endCaptures": {
"1": {
"name": "punctuation.separator.dictionary.pair.json"
}
},
"name": "meta.structure.dictionary.value.json",
"patterns": [
{
"comment": "the JSON object value",
"include": "#value"
},
{
"match": "[^\\s,]",
"name": "invalid.illegal.expected-dictionary-separator.json"
}
]
},
{
"match": "[^\\s\\}]",
"name": "invalid.illegal.expected-dictionary-separator.json"
}
]
},
"string": {
"begin": "\"",
"beginCaptures": {
"0": {
"name": "punctuation.definition.string.begin.json"
}
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.json"
}
},
"name": "string.quoted.double.json",
"patterns": [
{
"include": "#stringcontent"
}
]
},
"objectkey": {
"begin": "\"",
"beginCaptures": {
"0": {
"name": "punctuation.support.type.property-name.begin.json"
}
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.support.type.property-name.end.json"
}
},
"name": "support.type.property-name.json",
"patterns": [
{
"include": "#stringcontent"
}
]
},
"stringcontent": {
"patterns": [
{
"match": "\\\\(?:[\"\\\\/bfnrt]|u[0-9a-fA-F]{4})",
"name": "constant.character.escape.json"
},
{
"match": "\\\\.",
"name": "invalid.illegal.unrecognized-string-escape.json"
}
]
},
"value": {
"patterns": [
{
"include": "#constant"
},
{
"include": "#number"
},
{
"include": "#string"
},
{
"include": "#array"
},
{
"include": "#object"
},
{
"include": "#comments"
}
]
}
},
"scopeName": "source.json",
"uuid": "8f97457b-516e-48ce-83c7-08ae12fb327a"
}

View File

@ -0,0 +1,98 @@
{
"name": "Python",
"version": "1.0.0",
"fileTypes": ["py"],
"scopeName": "source.python",
"foldingStartMarker": "\\b(?:def|class)\\s*[^:]*:\\s*$",
"foldingStopMarker": "^\\s*\\}",
"patterns": [
{ "include": "#comments" },
{ "include": "#keywords" },
{ "include": "#constants-and-special-vars" },
{ "include": "#operators" },
{ "include": "#strings" }
],
"repository": {
"comments": {
"patterns": [
{ "name": "comment.line.hash.python", "match": "#.*$" },
{ "name": "comment.block.python", "begin": "'''", "end": "'''" },
{ "name": "comment.block.python", "begin": "\"\"\"", "end": "\"\"\"" }
]
},
"keywords": {
"patterns": [
{
"name": "keyword.control.python",
"match": "\\b(?:if|else|while|for|in|break|continue|return)\\b"
},
{
"name": "keyword.operator.logical.python",
"match": "\\b(?:and|or|not)\\b"
},
{ "name": "keyword.operator.assignment.python", "match": "=" },
{ "name": "storage.modifier.python", "match": "\\b(?:def|class)\\b" }
]
},
"constants-and-special-vars": {
"patterns": [
{
"name": "constant.language.python",
"match": "\\b(?:True|False|None)\\b"
},
{ "name": "variable.language.python", "match": "\\b(?:self)\\b" },
{
"name": "constant.numeric.python",
"match": "\\b(?:\\d+\\.?\\d*|\\.\\d+)\\b"
}
]
},
"operators": {
"patterns": [
{
"name": "keyword.operator.arithmetic.python",
"match": "\\b(?:\\+|-|\\*|/|%|//)\\b"
},
{
"name": "keyword.operator.comparison.python",
"match": "\\b(?:==|!=|<|<=|>|>=)\\b"
},
{
"name": "keyword.operator.logical.python",
"match": "\\b(?:and|or|not)\\b"
}
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.triple.double.python",
"begin": "\"\"\"",
"end": "\"\"\""
},
{
"name": "string.quoted.triple.single.python",
"begin": "'''",
"end": "'''"
},
{
"name": "string.quoted.double.python",
"begin": "\"",
"end": "\"",
"patterns": [{ "include": "#string-escape" }]
},
{
"name": "string.quoted.single.python",
"begin": "'",
"end": "'",
"patterns": [{ "include": "#string-escape" }]
}
]
},
"string-escape": {
"patterns": [
{ "name": "constant.character.escape.python", "match": "\\\\[\"']" }
]
}
}
}

View File

@ -0,0 +1,145 @@
{
"fileTypes": ["sql", "ddl", "dml"],
"foldingStartMarker": "(?i)^\\s*(begin|if|loop)\\b",
"foldingStopMarker": "(?i)^\\s*(end)\\b",
"keyEquivalent": "^~S",
"name": "PL/pgSQL (Postgres)",
"patterns": [
{
"begin": "/\\*",
"end": "\\*/",
"name": "comment.block.postgres"
},
{
"match": "--.*$",
"name": "comment.line.double-dash.postgres"
},
{
"captures": {
"1": {
"name": "keyword.other.postgres"
},
"2": {
"name": "keyword.other.postgres"
}
},
"match": "(?i)^\\s*(create)(\\s+or\\s+replace)?\\s+",
"name": "meta.create.postgres"
},
{
"captures": {
"1": {
"name": "keyword.other.postgres"
},
"2": {
"name": "keyword.other.postgres"
},
"3": {
"name": "entity.name.type.postgres"
}
},
"match": "(?i)\\b(package)(\\s+body)?\\s+(\\S+)",
"name": "meta.package.postgres"
},
{
"captures": {
"1": {
"name": "keyword.other.postgres"
},
"2": {
"name": "entity.name.type.postgres"
}
},
"match": "(?i)\\b(type)\\s+\"([^\"]+)\"",
"name": "meta.type.postgres"
},
{
"captures": {
"1": {
"name": "keyword.other.postgres"
},
"2": {
"name": "entity.name.function.postgres"
}
},
"match": "(?i)\\s*(function|procedure)\\s+([-a-z0-9_.]+)",
"name": "meta.procedure.postgres"
},
{
"match": "[!<>:]?=|<>|<|>|\\+|(?<!\\.)\\*|-|(?<!^)/|@@|\\|\\|",
"name": "keyword.operator.postgres"
},
{
"match": "(?i)\\b(true|false|null|found)\\b",
"name": "constant.language.postgres"
},
{
"match": "\\b\\d+(\\.\\d+)?\\b",
"name": "constant.numeric.postgres"
},
{
"match": "(?i)\\b(if|elsif|else|end\\s+if|loop|end\\s+loop|for|foreach|array|case|end\\s+case|continue|return|goto|alias)\\b",
"name": "keyword.control.postgres"
},
{
"match": "(?i)\\b(or|and|not|like)\\b",
"name": "keyword.operator.postgres"
},
{
"match": "(?i)\\b(sysdate|%(isopen|found|notfound|rowcount)|commit|rollback|sqlerrm|substr|cast|decode|length|lower|upper|coalesce)\\b",
"name": "support.function.postgres"
},
{
"match": "(?i)\\b(avg|count|sum|max|min|nvl|trim|to_date|to_char|lpad|ltrim|rpad|rtrim|trunc|to_number|regexp_split_to_array|regexp_replace)\\b",
"name": "support.function.builtin.postgres"
},
{
"match": "(?i)\\b(sql|sqlcode)\\b",
"name": "variable.language.postgres"
},
{
"match": "(?i)\\b(p(i|o|io)_[-a-z0-9_]+)\\b",
"name": "variable.parameter.postgres"
},
{
"match": "(?i)\\b(l_[-a-z0-9_]+)\\b",
"name": "variable.other.postgres"
},
{
"match": "(?i)\\b(immutable|volatile|stable|serial|primary|key|references|comment|column|schema|authorization|get|diagnostics|returning|drop|all|raise|notice|warning|exception|external|security|definer|language|grant|execute|on|to|function|procedure|returns|end|then|deterministic|exception|when|others|subtype|constant|range|binary_integer|declare|begin|in|out|is|as|exit|open|fetch|into|close|type|rowtype|default|\\.(extend|count|first|last|next|nextval|currval)|cost|alter|owner)\\b",
"name": "keyword.other.postgres"
},
{
"match": "(?i)\\b(select|perform|from|where|order\\s+by|group\\s+by|asc|desc|update|set|insert|into|values|delete|from|distinct|union|having|limit|table|of|prepare|(inner|left|outer) join)\\b",
"name": "keyword.other.sql.postgres"
},
{
"match": "[$][0-9]+",
"name": "storage.type.postgres"
},
{
"match": "(?i)\\b(dbms_lock|dbms_output)\\b",
"name": "support.class.postgres"
},
{
"match": "(?i)\\b(put_line)\\b",
"name": "support.function.postgres"
},
{
"begin": "'",
"end": "'",
"name": "string.quoted.single.postgres"
},
{
"begin": "\"",
"end": "\"",
"name": "string.quoted.double.postgres"
},
{
"match": "(?i)\\b(number|integer|bigint|varchar2|varchar|boolean|date|setof|record|query|numeric|void|character varying|text|([-a-z0-9_.]+%(row)?type))\\b",
"name": "storage.type.postgres"
}
],
"scopeName": "source.plpgsql.postgres",
"uuid": "28DCE4DD-F5E1-4ED3-8847-64DA6B1F9163"
}

View File

@ -0,0 +1,66 @@
{
"name": "YAML",
"fileTypes": ["yaml", "yml"],
"scopeName": "source.yaml",
"patterns": [
{
"name": "comment.line.number-sign.yaml",
"match": "#.*",
"captures": {
"0": {
"name": "punctuation.definition.comment.yaml"
}
}
},
{
"name": "entity.name.tag.yaml",
"match": "^\\s*\\w+",
"captures": {
"0": {
"name": "punctuation.definition.tag.yaml"
}
}
},
{
"name": "punctuation.separator.key-value.yaml",
"match": ":",
"captures": {
"0": {
"name": "punctuation.separator.key-value.yaml"
}
}
},
{
"name": "string.quoted.double.yaml",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.yaml",
"match": "\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{6}|.)"
}
]
},
{
"name": "string.quoted.single.yaml",
"begin": "'",
"end": "'",
"patterns": [
{
"name": "constant.character.escape.yaml",
"match": "''"
}
]
}
],
"repository": {
"scalar-plain": {
"patterns": [
{
"match": "\\b(\\w+)\\b",
"name": "scalar.plain.yaml"
}
]
}
}
}

View File

@ -468,5 +468,14 @@
"all": "All", "all": "All",
"unablePreview": "Unable to preview", "unablePreview": "Unable to preview",
"dashboardNav": "Dash", "dashboardNav": "Dash",
"accountNav": "You" "accountNav": "You",
"performance": "Performance",
"animatedMessageList": "Non-animated message list",
"animatedMessageListDesc": "Remove animation effects in message list, to reduce cause lag",
"theme": "Theme",
"globalTheme": "Global theme",
"agedTheme": "Old school style theme",
"agedThemeDesc": "Downgrade the global theme to Material Design 2. Unexpected issues may occur. For experimental use only.",
"appBackgroundImage": "Global background image",
"appBackgroundImageDesc": "The global background image will be displayed on all pages"
} }

View File

@ -266,7 +266,7 @@
"channelMembersAddHint": "到 @channel", "channelMembersAddHint": "到 @channel",
"channelType": "频道类型", "channelType": "频道类型",
"channelTypeCommon": "普通频道", "channelTypeCommon": "普通频道",
"channelTypeDirect": "私信聊天", "channelTypeDirect": "私信",
"channelAdjust": "调整频道", "channelAdjust": "调整频道",
"channelDetail": "频道详情", "channelDetail": "频道详情",
"channelSettings": "频道设置", "channelSettings": "频道设置",
@ -464,5 +464,14 @@
"all": "全部", "all": "全部",
"unablePreview": "无法预览", "unablePreview": "无法预览",
"dashboardNav": "仪表盘", "dashboardNav": "仪表盘",
"accountNav": "您" "accountNav": "您",
"performance": "性能",
"animatedMessageList": "无动画消息列表",
"animatedMessageListDesc": "在消息列表中禁用动画效果",
"theme": "主题",
"globalTheme": "全局应用主题",
"agedTheme": "过时主题",
"agedThemeDesc": "将全局主题降级为 Material Design 2可能发生意料之外的问题仅供实验使用",
"appBackgroundImage": "全局背景图片",
"appBackgroundImageDesc": "全局背景图片将会在所有页面中展示"
} }

View File

@ -38,45 +38,45 @@ PODS:
- file_picker (0.0.1): - file_picker (0.0.1):
- DKImagePickerController/PhotoGallery - DKImagePickerController/PhotoGallery
- Flutter - Flutter
- Firebase/Analytics (11.0.0): - Firebase/Analytics (11.2.0):
- Firebase/Core - Firebase/Core
- Firebase/Core (11.0.0): - Firebase/Core (11.2.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseAnalytics (~> 11.0.0) - FirebaseAnalytics (~> 11.2.0)
- Firebase/CoreOnly (11.0.0): - Firebase/CoreOnly (11.2.0):
- FirebaseCore (= 11.0.0) - FirebaseCore (= 11.2.0)
- Firebase/Crashlytics (11.0.0): - Firebase/Crashlytics (11.2.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseCrashlytics (~> 11.0.0) - FirebaseCrashlytics (~> 11.2.0)
- Firebase/Messaging (11.0.0): - Firebase/Messaging (11.2.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseMessaging (~> 11.0.0) - FirebaseMessaging (~> 11.2.0)
- Firebase/Performance (11.0.0): - Firebase/Performance (11.2.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebasePerformance (~> 11.0.0) - FirebasePerformance (~> 11.2.0)
- firebase_analytics (11.3.2): - firebase_analytics (11.3.3):
- Firebase/Analytics (= 11.0.0) - Firebase/Analytics (= 11.2.0)
- firebase_core - firebase_core
- Flutter - Flutter
- firebase_core (3.5.0): - firebase_core (3.6.0):
- Firebase/CoreOnly (= 11.0.0) - Firebase/CoreOnly (= 11.2.0)
- Flutter - Flutter
- firebase_crashlytics (4.1.2): - firebase_crashlytics (4.1.3):
- Firebase/Crashlytics (= 11.0.0) - Firebase/Crashlytics (= 11.2.0)
- firebase_core - firebase_core
- Flutter - Flutter
- firebase_messaging (15.1.2): - firebase_messaging (15.1.3):
- Firebase/Messaging (= 11.0.0) - Firebase/Messaging (= 11.2.0)
- firebase_core - firebase_core
- Flutter - Flutter
- firebase_performance (0.10.0-7): - firebase_performance (0.10.0-8):
- Firebase/Performance (= 11.0.0) - Firebase/Performance (= 11.2.0)
- firebase_core - firebase_core
- Flutter - Flutter
- FirebaseABTesting (11.2.0): - FirebaseABTesting (11.3.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseAnalytics (11.0.0): - FirebaseAnalytics (11.2.0):
- FirebaseAnalytics/AdIdSupport (= 11.0.0) - FirebaseAnalytics/AdIdSupport (= 11.2.0)
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@ -84,24 +84,24 @@ PODS:
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.0.0): - FirebaseAnalytics/AdIdSupport (11.2.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.0.0) - GoogleAppMeasurement (= 11.2.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseCore (11.0.0): - FirebaseCore (11.2.0):
- FirebaseCoreInternal (~> 11.0) - FirebaseCoreInternal (~> 11.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0) - GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreExtension (11.2.0): - FirebaseCoreExtension (11.3.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseCoreInternal (11.2.0): - FirebaseCoreInternal (11.3.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseCrashlytics (11.0.0): - FirebaseCrashlytics (11.2.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- FirebaseRemoteConfigInterop (~> 11.0) - FirebaseRemoteConfigInterop (~> 11.0)
@ -110,12 +110,12 @@ PODS:
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
- FirebaseInstallations (11.2.0): - FirebaseInstallations (11.3.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
- FirebaseMessaging (11.0.0): - FirebaseMessaging (11.2.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0) - GoogleDataTransport (~> 10.0)
@ -124,7 +124,7 @@ PODS:
- GoogleUtilities/Reachability (~> 8.0) - GoogleUtilities/Reachability (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebasePerformance (11.0.0): - FirebasePerformance (11.2.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- FirebaseRemoteConfig (~> 11.0) - FirebaseRemoteConfig (~> 11.0)
@ -134,7 +134,7 @@ PODS:
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseRemoteConfig (11.2.0): - FirebaseRemoteConfig (11.3.0):
- FirebaseABTesting (~> 11.0) - FirebaseABTesting (~> 11.0)
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
@ -142,8 +142,8 @@ PODS:
- FirebaseSharedSwift (~> 11.0) - FirebaseSharedSwift (~> 11.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseRemoteConfigInterop (11.2.0) - FirebaseRemoteConfigInterop (11.3.0)
- FirebaseSessions (11.2.0): - FirebaseSessions (11.3.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseCoreExtension (~> 11.0) - FirebaseCoreExtension (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
@ -152,7 +152,7 @@ PODS:
- GoogleUtilities/UserDefaults (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- PromisesSwift (~> 2.1) - PromisesSwift (~> 2.1)
- FirebaseSharedSwift (11.2.0) - FirebaseSharedSwift (11.3.0)
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_app_update (0.0.1): - flutter_app_update (0.0.1):
- Flutter - Flutter
@ -172,21 +172,21 @@ PODS:
- gal (1.0.0): - gal (1.0.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- GoogleAppMeasurement (11.0.0): - GoogleAppMeasurement (11.2.0):
- GoogleAppMeasurement/AdIdSupport (= 11.0.0) - GoogleAppMeasurement/AdIdSupport (= 11.2.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.0.0): - GoogleAppMeasurement/AdIdSupport (11.2.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.0.0) - GoogleAppMeasurement/WithoutAdIdSupport (= 11.2.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.0.0): - GoogleAppMeasurement/WithoutAdIdSupport (11.2.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
@ -454,25 +454,25 @@ SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9 Firebase: 98e6bf5278170668a7983e12971a66b2cd57fc8c
firebase_analytics: 4fd10182fd08bb8358f26ac8aca8dad7b6d0f592 firebase_analytics: fbc57838bdb94eef1e0ff504f127d974ff2981ad
firebase_core: 2ec6b789859c7c24766344ec71fdf78639402d56 firebase_core: 2bedc3136ec7c7b8561c6123ed0239387b53f2af
firebase_crashlytics: 60630a0f91ee432275fa1660fd8593079761448a firebase_crashlytics: 37d104d457b51760b48504a93a12b3bf70995d77
firebase_messaging: a18e1e02b2e8e69097c8173e0c851be223b21c50 firebase_messaging: 15d114e1a41fc31e4fbabcd48d765a19eec94a38
firebase_performance: 12d45fdf120992fa879d990929bf73d4a5ced053 firebase_performance: 26ad47755d3e8d7b04b9bb36bdfbf1cec8d8dfcc
FirebaseABTesting: 2104d957ce33888a3d6f3bde298cdee376dde8f1 FirebaseABTesting: c4559fcd2eba9f6bdaf0599e2c37ded01c343e4c
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a FirebaseAnalytics: c36efd5710c60c17558650fa58c2066eca7e9265
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383 FirebaseCore: a282032ae9295c795714ded2ec9c522fc237f8da
FirebaseCoreExtension: cda74ddfb001224bd8fd1d6e74698b4ed07803de FirebaseCoreExtension: 30bb063476ef66cd46925243d64ad8b2c8ac3264
FirebaseCoreInternal: 0c569513412da9f3b31bd0b340013bbee8f295c5 FirebaseCoreInternal: ac26d09a70c730e497936430af4e60fb0c68ec4e
FirebaseCrashlytics: 745d8f0221fe49c62865391d1bf56f5a12eeec0b FirebaseCrashlytics: cfc69af5b53565dc6a5e563788809b5778ac4eac
FirebaseInstallations: 771177d89d6c451dc6e50085ec82e2fc77ed0a4a FirebaseInstallations: 58cf94dabf1e2bb2fa87725a9be5c2249171cda0
FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742 FirebaseMessaging: c9ec7b90c399c7a6100297e9d16f8a27fc7f7152
FirebasePerformance: efdc02bacb1b4710588c9f867011605c081cdf79 FirebasePerformance: c39138c0700b8ef6040f0b80b5707320808e2862
FirebaseRemoteConfig: fca0b2d017fc1de52b28a4e5bcf2007c1a840457 FirebaseRemoteConfig: 5be2ca4f9870d475b39214210955fdaeecf7e5ca
FirebaseRemoteConfigInterop: 477b26fdeb8fb5fbaf22fa9db5343b42289dc7db FirebaseRemoteConfigInterop: c3a5c31b3c22079f41ba1dc645df889d9ce38cb9
FirebaseSessions: adcec8b72d0066a385e3affcd1bcb1ebb3908ce6 FirebaseSessions: 655ff17f3cc1a635cbdc2d69b953878001f9e25b
FirebaseSharedSwift: 7a0d78d155ede78407f0fdc89fbc914014c7c540 FirebaseSharedSwift: d39c2ad64a11a8d936ce25a42b00df47078bb59c
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac
@ -482,7 +482,7 @@ SPEC CHECKSUMS:
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
flutter_webrtc: 75b868e4f9e817c7a9a42ca4b6169063de4eec9f flutter_webrtc: 75b868e4f9e817c7a9a42ca4b6169063de4eec9f
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1 gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
GoogleAppMeasurement: 6e49ffac7d3f2c3ded9cc663f912a13b67bbd0de GoogleAppMeasurement: 76d4f8b36b03bd8381fa9a7fe2cc7f99c0a2e93a
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf

View File

@ -17,6 +17,7 @@ import 'package:solian/providers/relation.dart';
import 'package:solian/providers/theme_switcher.dart'; import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/websocket.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart'; import 'package:solian/widgets/sized_container.dart';
import 'package:flutter_app_update/flutter_app_update.dart'; import 'package:flutter_app_update/flutter_app_update.dart';
import 'package:version/version.dart'; import 'package:version/version.dart';
@ -255,8 +256,7 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_isBusy || _isErrored) { if (_isBusy || _isErrored) {
return GestureDetector( return GestureDetector(
child: Material( child: RootContainer(
color: Theme.of(context).colorScheme.surface,
child: Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,

50
lib/models/theme.dart Normal file
View File

@ -0,0 +1,50 @@
import 'dart:ui';
import 'package:json_annotation/json_annotation.dart';
part 'theme.g.dart';
@JsonSerializable(converters: [ColorConverter()])
class SolianThemeData {
String id;
Color seedColor;
String? fontFamily;
List<String>? fontFamilyFallback;
SolianThemeData({
required this.id,
required this.seedColor,
this.fontFamily,
this.fontFamilyFallback,
});
factory SolianThemeData.fromJson(Map<String, dynamic> json) =>
_$SolianThemeDataFromJson(json);
Map<String, dynamic> toJson() => _$SolianThemeDataToJson(this);
@override
int get hashCode => id.hashCode;
@override
bool operator ==(Object other) {
if (other is SolianThemeData) {
return id == other.id;
}
return false;
}
}
class ColorConverter extends JsonConverter<Color, int> {
const ColorConverter();
@override
Color fromJson(int json) {
return Color(json);
}
@override
int toJson(Color object) {
return object.value;
}
}

26
lib/models/theme.g.dart Normal file
View File

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'theme.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SolianThemeData _$SolianThemeDataFromJson(Map<String, dynamic> json) =>
SolianThemeData(
id: json['id'] as String,
seedColor:
const ColorConverter().fromJson((json['seed_color'] as num).toInt()),
fontFamily: json['font_family'] as String?,
fontFamilyFallback: (json['font_family_fallback'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
);
Map<String, dynamic> _$SolianThemeDataToJson(SolianThemeData instance) =>
<String, dynamic>{
'id': instance.id,
'seed_color': const ColorConverter().toJson(instance.seedColor),
'font_family': instance.fontFamily,
'font_family_fallback': instance.fontFamilyFallback,
};

View File

@ -1,5 +1,8 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/models/theme.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
class ThemeSwitcher extends ChangeNotifier { class ThemeSwitcher extends ChangeNotifier {
@ -13,11 +16,21 @@ class ThemeSwitcher extends ChangeNotifier {
Future<void> restoreTheme() async { Future<void> restoreTheme() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('global_theme_color')) { if (prefs.containsKey('global_theme')) {
final value = prefs.getInt('global_theme_color')!; final value = SolianThemeData.fromJson(
final color = Color(value); jsonDecode(prefs.getString('global_theme')!),
lightThemeData = AppTheme.build(Brightness.light, seedColor: color); );
darkThemeData = AppTheme.build(Brightness.dark, seedColor: color); final agedTheme = prefs.getBool('aged_theme');
lightThemeData = AppTheme.buildFromData(
Brightness.light,
value,
useMaterial3: agedTheme == null ? true : !agedTheme,
);
darkThemeData = AppTheme.buildFromData(
Brightness.dark,
value,
useMaterial3: agedTheme == null ? true : !agedTheme,
);
notifyListeners(); notifyListeners();
} }
} }
@ -27,4 +40,25 @@ class ThemeSwitcher extends ChangeNotifier {
darkThemeData = dark; darkThemeData = dark;
notifyListeners(); notifyListeners();
} }
Future<void> setThemeData(SolianThemeData? data) async {
final prefs = await SharedPreferences.getInstance();
if (data == null) {
prefs.remove('global_theme');
} else {
prefs.setString(
'global_theme',
jsonEncode(data.toJson()),
);
lightThemeData = AppTheme.buildFromData(Brightness.light, data);
darkThemeData = AppTheme.buildFromData(Brightness.dark, data);
notifyListeners();
}
}
Future<void> setAgedTheme(bool enabled) async {
final prefs = await SharedPreferences.getInstance();
prefs.setBool('aged_theme', enabled);
await restoreTheme();
}
} }

View File

@ -178,6 +178,7 @@ abstract class AppRouter {
final arguments = state.extra as ChannelDetailArguments; final arguments = state.extra as ChannelDetailArguments;
return TitleShell( return TitleShell(
state: state, state: state,
isResponsive: true,
child: ChannelDetailScreen( child: ChannelDetailScreen(
channel: arguments.channel, channel: arguments.channel,
profile: arguments.profile, profile: arguments.profile,

View File

@ -15,133 +15,130 @@ class AboutScreen extends StatelessWidget {
const denseButtonStyle = const denseButtonStyle =
ButtonStyle(visualDensity: VisualDensity(vertical: -4)); ButtonStyle(visualDensity: VisualDensity(vertical: -4));
return Material( return SizedBox(
color: Theme.of(context).colorScheme.surface, width: double.infinity,
child: SizedBox( child: Column(
width: double.infinity, mainAxisAlignment: MainAxisAlignment.center,
child: Column( children: [
mainAxisAlignment: MainAxisAlignment.center, ClipRRect(
children: [ borderRadius: const BorderRadius.all(Radius.circular(16)),
ClipRRect( child: Image.asset('assets/logo.png', width: 120, height: 120),
borderRadius: const BorderRadius.all(Radius.circular(16)), ),
child: Image.asset('assets/logo.png', width: 120, height: 120), const Gap(8),
), Text(
const Gap(8), 'Solian',
Text( style: Theme.of(context).textTheme.headlineMedium,
'Solian', ),
style: Theme.of(context).textTheme.headlineMedium, const Text(
), 'The Solar Network',
const Text( style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
'The Solar Network', ),
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), const Gap(8),
), FutureBuilder(
const Gap(8), future: PackageInfo.fromPlatform(),
FutureBuilder( builder: (context, snapshot) {
future: PackageInfo.fromPlatform(), if (!snapshot.hasData) {
builder: (context, snapshot) { return const SizedBox.shrink();
if (!snapshot.hasData) { }
return const SizedBox.shrink();
}
return Text( return Text(
'v${snapshot.data!.version} · ${snapshot.data!.buildNumber}', 'v${snapshot.data!.version} · ${snapshot.data!.buildNumber}',
style: const TextStyle(fontFamily: 'monospace'), style: const TextStyle(fontFamily: 'monospace'),
); );
}, },
), ),
Text('Copyright © ${DateTime.now().year} Solsynth LLC'), Text('Copyright © ${DateTime.now().year} Solsynth LLC'),
const Gap(16), const Gap(16),
CenteredContainer( CenteredContainer(
maxWidth: 280, maxWidth: 280,
child: Wrap( child: Wrap(
spacing: 4, spacing: 4,
runSpacing: 4, runSpacing: 4,
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
children: [ children: [
TextButton( TextButton(
style: denseButtonStyle, style: denseButtonStyle,
child: Text('appDetails'.tr), child: Text('appDetails'.tr),
onPressed: () async { onPressed: () async {
final info = await PackageInfo.fromPlatform(); final info = await PackageInfo.fromPlatform();
showAboutDialog( showAboutDialog(
context: context, context: context,
applicationVersion: applicationVersion:
'${info.version} (${info.buildNumber})', '${info.version} (${info.buildNumber})',
applicationLegalese: applicationLegalese:
'The Solar Network App is an intuitive and open-source social network and computing platform. Experience the freedom of a user-friendly design that empowers you to create and connect with communities on your own terms. Embrace the future of social networking with a platform that prioritizes your independence and privacy.', 'The Solar Network App is an intuitive and open-source social network and computing platform. Experience the freedom of a user-friendly design that empowers you to create and connect with communities on your own terms. Embrace the future of social networking with a platform that prioritizes your independence and privacy.',
applicationIcon: ClipRRect( applicationIcon: ClipRRect(
borderRadius: borderRadius:
const BorderRadius.all(Radius.circular(16)), const BorderRadius.all(Radius.circular(16)),
child: Image.asset('assets/logo.png', child: Image.asset('assets/logo.png',
width: 60, height: 60), width: 60, height: 60),
), ),
); );
}, },
), ),
TextButton( TextButton(
style: denseButtonStyle, style: denseButtonStyle,
child: Text('projectWebsite'.tr), child: Text('projectWebsite'.tr),
onPressed: () { onPressed: () {
launchUrlString( launchUrlString(
'https://solsynth.dev/products/solar-network'); 'https://solsynth.dev/products/solar-network');
}, },
), ),
TextButton( TextButton(
style: denseButtonStyle, style: denseButtonStyle,
child: Text('termRelated'.tr), child: Text('termRelated'.tr),
onPressed: () { onPressed: () {
launchUrlString('https://solsynth.dev/terms'); launchUrlString('https://solsynth.dev/terms');
}, },
), ),
TextButton( TextButton(
style: denseButtonStyle, style: denseButtonStyle,
child: Text('serviceStatus'.tr), child: Text('serviceStatus'.tr),
onPressed: () { onPressed: () {
launchUrlString('https://status.solsynth.dev'); launchUrlString('https://status.solsynth.dev');
}, },
), ),
], ],
),
), ),
const Gap(16), ),
const Text( const Gap(16),
'Open-sourced under AGPLv3', const Text(
style: TextStyle( 'Open-sourced under AGPLv3',
style: TextStyle(
fontWeight: FontWeight.w300,
fontSize: 12,
),
),
FutureBuilder(
future: SharedPreferences.getInstance(),
builder: (context, snapshot) {
const textStyle = TextStyle(
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
fontSize: 12, fontSize: 12,
), );
), if (!snapshot.hasData ||
FutureBuilder( !snapshot.data!.containsKey('first_boot_time')) {
future: SharedPreferences.getInstance(), return Text(
builder: (context, snapshot) { 'firstBootTime'.trParams({'time': 'unknown'.tr}),
const textStyle = TextStyle( style: textStyle,
fontWeight: FontWeight.w300,
fontSize: 12,
); );
if (!snapshot.hasData || } else {
!snapshot.data!.containsKey('first_boot_time')) { return Text(
return Text( 'firstBootTime'.trParams({
'firstBootTime'.trParams({'time': 'unknown'.tr}), 'time': DateFormat('yyyy-MM-dd').format(
style: textStyle, DateTime.tryParse(
); snapshot.data!.getString('first_boot_time')!,
} else { )?.toLocal() ??
return Text( DateTime.now(),
'firstBootTime'.trParams({ ),
'time': DateFormat('yyyy-MM-dd').format( }),
DateTime.tryParse( style: textStyle,
snapshot.data!.getString('first_boot_time')!, );
)?.toLocal() ?? }
DateTime.now(), },
), ),
}), ],
style: textStyle,
);
}
},
),
],
),
), ),
); );
} }

View File

@ -49,113 +49,110 @@ class _AccountScreenState extends State<AccountScreen> {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
return Material( return SafeArea(
color: Theme.of(context).colorScheme.surface, child: Obx(() {
child: SafeArea( if (auth.isAuthorized.isFalse) {
child: Obx(() { return Center(
if (auth.isAuthorized.isFalse) { child: Column(
return Center( mainAxisSize: MainAxisSize.min,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_ActionCard(
icon: Icon(
Icons.login,
color: Theme.of(context).colorScheme.onPrimary,
),
title: 'signin'.tr,
caption: 'signinCaption'.tr,
onTap: () {
AppRouter.instance.pushNamed('signin').then((val) async {
if (val == true) {
await auth.refreshUserProfile();
}
});
},
),
_ActionCard(
icon: Icon(
Icons.add,
color: Theme.of(context).colorScheme.onPrimary,
),
title: 'signup'.tr,
caption: 'signupCaption'.tr,
onTap: () {
AppRouter.instance.pushNamed('signup').then((_) {
setState(() {});
});
},
),
const Gap(4),
TextButton(
style: const ButtonStyle(
visualDensity: VisualDensity(
horizontal: -4,
vertical: -2,
),
),
onPressed: () {
AppRouter.instance.pushNamed('settings');
},
child: Text('settings'.tr),
),
],
),
);
}
return CenteredContainer(
child: ListView(
children: [ children: [
if (auth.userProfile.value != null) _ActionCard(
const AccountHeading().paddingOnly(bottom: 8, top: 8), icon: Icon(
...(actionItems.map( Icons.login,
(x) => ListTile( color: Theme.of(context).colorScheme.onPrimary,
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
leading: x.$1,
title: Text(x.$2),
onTap: () {
AppRouter.instance
.pushNamed(x.$3)
.then((_) => setState(() {}));
},
), ),
)), title: 'signin'.tr,
const Divider(thickness: 0.3, height: 1) caption: 'signinCaption'.tr,
.paddingSymmetric(vertical: 4),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
leading: const Icon(Icons.settings),
title: Text('settings'.tr),
onTap: () { onTap: () {
AppRouter.instance.pushNamed('settings'); AppRouter.instance.pushNamed('signin').then((val) async {
if (val == true) {
await auth.refreshUserProfile();
}
});
}, },
), ),
if (auth.isAuthorized.value) _ActionCard(
ListTile( icon: Icon(
contentPadding: const EdgeInsets.symmetric(horizontal: 34), Icons.add,
leading: const Icon(Icons.edit_notifications), color: Theme.of(context).colorScheme.onPrimary,
title: Text('notificationPreferences'.tr),
onTap: () {
AppRouter.instance.pushNamed('notificationPreferences');
},
), ),
const Divider(thickness: 0.3, height: 1) title: 'signup'.tr,
.paddingSymmetric(vertical: 4), caption: 'signupCaption'.tr,
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
leading: const Icon(Icons.logout),
title: Text('signout'.tr),
onTap: () { onTap: () {
auth.signout(); AppRouter.instance.pushNamed('signup').then((_) {
setState(() {}); setState(() {});
});
}, },
), ),
const Gap(4),
TextButton(
style: const ButtonStyle(
visualDensity: VisualDensity(
horizontal: -4,
vertical: -2,
),
),
onPressed: () {
AppRouter.instance.pushNamed('settings');
},
child: Text('settings'.tr),
),
], ],
), ),
); );
}), }
),
return CenteredContainer(
child: ListView(
children: [
if (auth.userProfile.value != null)
const AccountHeading().paddingOnly(bottom: 8, top: 16),
...(actionItems.map(
(x) => ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
leading: x.$1,
title: Text(x.$2),
onTap: () {
AppRouter.instance
.pushNamed(x.$3)
.then((_) => setState(() {}));
},
),
)),
const Divider(thickness: 0.3, height: 1)
.paddingSymmetric(vertical: 4),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
leading: const Icon(Icons.settings),
title: Text('settings'.tr),
onTap: () {
AppRouter.instance.pushNamed('settings');
},
),
if (auth.isAuthorized.value)
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
leading: const Icon(Icons.edit_notifications),
title: Text('notificationPreferences'.tr),
onTap: () {
AppRouter.instance.pushNamed('notificationPreferences');
},
),
const Divider(thickness: 0.3, height: 1)
.paddingSymmetric(vertical: 4),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
leading: const Icon(Icons.logout),
title: Text('signout'.tr),
onTap: () {
auth.signout();
setState(() {});
},
),
],
),
);
}),
); );
} }
} }

View File

@ -6,6 +6,7 @@ import 'package:solian/models/relations.dart';
import 'package:solian/providers/relation.dart'; import 'package:solian/providers/relation.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
import 'package:solian/widgets/account/relative_list.dart'; import 'package:solian/widgets/account/relative_list.dart';
import 'package:solian/widgets/root_container.dart';
class FriendScreen extends StatefulWidget { class FriendScreen extends StatefulWidget {
const FriendScreen({super.key}); const FriendScreen({super.key});
@ -117,8 +118,7 @@ class _FriendScreenState extends State<FriendScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
centerTitle: false, centerTitle: false,

View File

@ -74,45 +74,42 @@ class _NotificationPreferencesScreenState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Column(
color: Theme.of(context).colorScheme.surface, children: [
child: Column( if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
children: [ ListTile(
if (_isBusy) const LinearProgressIndicator().animate().scaleX(), tileColor: Theme.of(context).colorScheme.surfaceContainer,
ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24),
tileColor: Theme.of(context).colorScheme.surfaceContainer, leading: const Icon(Icons.save),
contentPadding: const EdgeInsets.symmetric(horizontal: 24), title: Text('save'.tr),
leading: const Icon(Icons.save), enabled: !_isBusy,
title: Text('save'.tr), onTap: () {
enabled: !_isBusy, _savePreferences();
onTap: () { },
_savePreferences(); ),
Expanded(
child: ListView.builder(
itemCount: _topicMap.length,
itemBuilder: (context, index) {
final element = _topicMap.entries.elementAt(index);
return CheckboxListTile(
title: Text(element.value),
subtitle: Text(
element.key,
style: GoogleFonts.robotoMono(fontSize: 12),
),
value: _config[element.key] ?? true,
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
onChanged: (value) {
setState(() {
_config[element.key] = value ?? false;
});
},
);
}, },
), ),
Expanded( ),
child: ListView.builder( ],
itemCount: _topicMap.length,
itemBuilder: (context, index) {
final element = _topicMap.entries.elementAt(index);
return CheckboxListTile(
title: Text(element.value),
subtitle: Text(
element.key,
style: GoogleFonts.robotoMono(fontSize: 12),
),
value: _config[element.key] ?? true,
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
onChanged: (value) {
setState(() {
_config[element.key] = value ?? false;
});
},
);
},
),
),
],
),
); );
} }
} }

View File

@ -186,164 +186,161 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
const double padding = 32; const double padding = 32;
return Material( return ListView(
color: Theme.of(context).colorScheme.surface, children: [
child: ListView( if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
children: [ const Gap(24),
if (_isBusy) const LinearProgressIndicator().animate().scaleX(), Stack(
const Gap(24), children: [
Stack( AccountAvatar(content: _avatar, radius: 40),
children: [ Positioned(
AccountAvatar(content: _avatar, radius: 40), bottom: 0,
Positioned( left: 40,
bottom: 0, child: FloatingActionButton.small(
left: 40, heroTag: const Key('avatar-editor'),
child: FloatingActionButton.small( onPressed: () => _editImage('avatar'),
heroTag: const Key('avatar-editor'), child: const Icon(
onPressed: () => _editImage('avatar'), Icons.camera,
child: const Icon(
Icons.camera,
),
), ),
), ),
],
).paddingSymmetric(horizontal: padding),
const Gap(16),
Stack(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: _banner != null
? Image.network(
ServiceFinder.buildUrl(
'files', '/attachments/$_banner'),
fit: BoxFit.cover,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes !=
null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
)
: Container(),
),
),
),
Positioned(
bottom: 16,
right: 16,
child: FloatingActionButton(
heroTag: const Key('banner-editor'),
onPressed: () => _editImage('banner'),
child: const Icon(
Icons.camera_alt,
),
),
),
],
).paddingSymmetric(horizontal: padding),
const Gap(24),
Row(
children: [
Flexible(
flex: 1,
child: TextField(
readOnly: true,
controller: _usernameController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'username'.tr,
prefixText: '@',
),
),
),
const Gap(16),
Flexible(
flex: 1,
child: TextField(
controller: _nicknameController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'nickname'.tr,
),
),
),
],
).paddingSymmetric(horizontal: padding),
const Gap(16),
Row(
children: [
Flexible(
flex: 1,
child: TextField(
controller: _firstNameController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'firstName'.tr,
),
),
),
const Gap(16),
Flexible(
flex: 1,
child: TextField(
controller: _lastNameController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'lastName'.tr,
),
),
),
],
).paddingSymmetric(horizontal: padding),
const Gap(16),
TextField(
controller: _descriptionController,
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 3,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'description'.tr,
), ),
).paddingSymmetric(horizontal: padding), ],
const Gap(16), ).paddingSymmetric(horizontal: padding),
TextField( const Gap(16),
controller: _birthdayController, Stack(
readOnly: true, children: [
decoration: InputDecoration( ClipRRect(
border: const OutlineInputBorder(), borderRadius: const BorderRadius.all(Radius.circular(8)),
labelText: 'birthday'.tr, child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: _banner != null
? Image.network(
ServiceFinder.buildUrl(
'files', '/attachments/$_banner'),
fit: BoxFit.cover,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes !=
null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
)
: Container(),
),
),
), ),
onTap: () => _selectBirthday(), Positioned(
).paddingSymmetric(horizontal: padding), bottom: 16,
const Gap(16), right: 16,
Row( child: FloatingActionButton(
mainAxisAlignment: MainAxisAlignment.end, heroTag: const Key('banner-editor'),
children: [ onPressed: () => _editImage('banner'),
TextButton( child: const Icon(
onPressed: _isBusy ? null : () => _syncWidget(), Icons.camera_alt,
child: Text('reset'.tr), ),
), ),
ElevatedButton( ),
onPressed: _isBusy ? null : () => _editUserInfo(), ],
child: Text('apply'.tr), ).paddingSymmetric(horizontal: padding),
const Gap(24),
Row(
children: [
Flexible(
flex: 1,
child: TextField(
readOnly: true,
controller: _usernameController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'username'.tr,
prefixText: '@',
),
), ),
], ),
).paddingSymmetric(horizontal: padding), const Gap(16),
], Flexible(
), flex: 1,
child: TextField(
controller: _nicknameController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'nickname'.tr,
),
),
),
],
).paddingSymmetric(horizontal: padding),
const Gap(16),
Row(
children: [
Flexible(
flex: 1,
child: TextField(
controller: _firstNameController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'firstName'.tr,
),
),
),
const Gap(16),
Flexible(
flex: 1,
child: TextField(
controller: _lastNameController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'lastName'.tr,
),
),
),
],
).paddingSymmetric(horizontal: padding),
const Gap(16),
TextField(
controller: _descriptionController,
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 3,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'description'.tr,
),
).paddingSymmetric(horizontal: padding),
const Gap(16),
TextField(
controller: _birthdayController,
readOnly: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'birthday'.tr,
),
onTap: () => _selectBirthday(),
).paddingSymmetric(horizontal: padding),
const Gap(16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: _isBusy ? null : () => _syncWidget(),
child: Text('reset'.tr),
),
ElevatedButton(
onPressed: _isBusy ? null : () => _editUserInfo(),
child: Text('apply'.tr),
),
],
).paddingSymmetric(horizontal: padding),
],
); );
} }

View File

@ -28,6 +28,7 @@ import 'package:solian/widgets/daily_sign/history_chart.dart';
import 'package:solian/widgets/posts/post_list.dart'; import 'package:solian/widgets/posts/post_list.dart';
import 'package:solian/widgets/posts/post_warped_list.dart'; import 'package:solian/widgets/posts/post_warped_list.dart';
import 'package:solian/widgets/reports/abuse_report.dart'; import 'package:solian/widgets/reports/abuse_report.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart'; import 'package:solian/widgets/sized_container.dart';
class AccountProfilePage extends StatefulWidget { class AccountProfilePage extends StatefulWidget {
@ -230,11 +231,14 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_isBusy || _userinfo == null) { if (_isBusy || _userinfo == null) {
return const Center(child: CircularProgressIndicator()); return RootContainer(
child: const Center(
child: CircularProgressIndicator(),
),
);
} }
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: DefaultTabController( child: DefaultTabController(
length: 3, length: 3,
child: NestedScrollView( child: NestedScrollView(
@ -250,7 +254,11 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
height: 56, height: 56,
child: Row( child: Row(
children: [ children: [
AppBarLeadingButton.adaptive(context) ?? const Gap(8), AppBarLeadingButton.adaptive(
context,
forceBack: true,
) ??
const Gap(8),
const Gap(8), const Gap(8),
if (_userinfo != null) if (_userinfo != null)
AccountAvatar(content: _userinfo!.avatar, radius: 16), AccountAvatar(content: _userinfo!.avatar, radius: 16),

View File

@ -216,10 +216,10 @@ class _SignInScreenState extends State<SignInScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return CenteredContainer(
color: Theme.of(context).colorScheme.surface, maxWidth: 360,
child: CenteredContainer( child: Theme(
maxWidth: 360, data: Theme.of(context).copyWith(canvasColor: Colors.transparent),
child: PageTransitionSwitcher( child: PageTransitionSwitcher(
transitionBuilder: ( transitionBuilder: (
Widget child, Widget child,

View File

@ -65,148 +65,141 @@ class _SignUpScreenState extends State<SignUpScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return CenteredContainer(
color: Theme.of(context).colorScheme.surface, maxWidth: 360,
child: CenteredContainer( child: ListView(
maxWidth: 360, shrinkWrap: true,
child: ListView( children: [
shrinkWrap: true, Align(
children: [ alignment: Alignment.centerLeft,
Align( child: ClipRRect(
alignment: Alignment.centerLeft, borderRadius: const BorderRadius.all(Radius.circular(8)),
child: ClipRRect( child: Image.asset('assets/logo.png', width: 64, height: 64),
borderRadius: const BorderRadius.all(Radius.circular(8)), ).paddingOnly(bottom: 8, left: 4),
child: Image.asset('assets/logo.png', width: 64, height: 64), ),
).paddingOnly(bottom: 8, left: 4), Text(
'signupGreeting'.tr,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w900,
), ),
Text( ).paddingOnly(left: 4, bottom: 16),
'signupGreeting'.tr, TextField(
style: const TextStyle( autocorrect: false,
fontSize: 28, enableSuggestions: false,
fontWeight: FontWeight.w900, controller: _usernameController,
), autofillHints: const [AutofillHints.username],
).paddingOnly(left: 4, bottom: 16), decoration: InputDecoration(
TextField( isDense: true,
autocorrect: false, border: const OutlineInputBorder(),
enableSuggestions: false, labelText: 'username'.tr,
controller: _usernameController,
autofillHints: const [AutofillHints.username],
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'username'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
), ),
const Gap(12), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
TextField( ),
autocorrect: false, const Gap(12),
enableSuggestions: false, TextField(
controller: _nicknameController, autocorrect: false,
autofillHints: const [AutofillHints.nickname], enableSuggestions: false,
decoration: InputDecoration( controller: _nicknameController,
isDense: true, autofillHints: const [AutofillHints.nickname],
border: const OutlineInputBorder(), decoration: InputDecoration(
labelText: 'nickname'.tr, isDense: true,
), border: const OutlineInputBorder(),
onTapOutside: (_) => labelText: 'nickname'.tr,
FocusManager.instance.primaryFocus?.unfocus(),
), ),
const Gap(12), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
TextField( ),
autocorrect: false, const Gap(12),
enableSuggestions: false, TextField(
controller: _emailController, autocorrect: false,
autofillHints: const [AutofillHints.email], enableSuggestions: false,
decoration: InputDecoration( controller: _emailController,
isDense: true, autofillHints: const [AutofillHints.email],
border: const OutlineInputBorder(), decoration: InputDecoration(
labelText: 'email'.tr, isDense: true,
), border: const OutlineInputBorder(),
onTapOutside: (_) => labelText: 'email'.tr,
FocusManager.instance.primaryFocus?.unfocus(),
), ),
const Gap(12), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
TextField( ),
obscureText: true, const Gap(12),
autocorrect: false, TextField(
enableSuggestions: false, obscureText: true,
autofillHints: const [AutofillHints.password], autocorrect: false,
controller: _passwordController, enableSuggestions: false,
decoration: InputDecoration( autofillHints: const [AutofillHints.password],
isDense: true, controller: _passwordController,
border: const OutlineInputBorder(), decoration: InputDecoration(
labelText: 'password'.tr, isDense: true,
), border: const OutlineInputBorder(),
onTapOutside: (_) => labelText: 'password'.tr,
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: (_) => _performAction(context),
), ),
const Gap(8), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
CheckboxListTile( onSubmitted: (_) => _performAction(context),
value: _isTermAccepted, ),
title: Text( const Gap(8),
'termAccept'.tr, CheckboxListTile(
style: const TextStyle(height: 1.2), value: _isTermAccepted,
).paddingOnly(bottom: 4), title: Text(
shape: const RoundedRectangleBorder( 'termAccept'.tr,
borderRadius: BorderRadius.all( style: const TextStyle(height: 1.2),
Radius.circular(8), ).paddingOnly(bottom: 4),
), shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8),
), ),
subtitle: RichText( ),
text: TextSpan( subtitle: RichText(
style: Theme.of(context).textTheme.bodySmall!.copyWith( text: TextSpan(
color: Theme.of(context) style: Theme.of(context).textTheme.bodySmall!.copyWith(
.colorScheme color: Theme.of(context)
.onSurface .colorScheme
.withOpacity(0.75), .onSurface
), .withOpacity(0.75),
children: [ ),
TextSpan(text: 'termAcceptDesc'.tr), children: [
WidgetSpan( TextSpan(text: 'termAcceptDesc'.tr),
child: Material( WidgetSpan(
color: Colors.transparent, child: Material(
child: InkWell( color: Colors.transparent,
child: Row( child: InkWell(
mainAxisSize: MainAxisSize.min, child: Row(
children: [ mainAxisSize: MainAxisSize.min,
Text('termAcceptLink'.tr), children: [
const Gap(4), Text('termAcceptLink'.tr),
const Icon(Icons.launch, size: 14), const Gap(4),
], const Icon(Icons.launch, size: 14),
), ],
onTap: () {
launchUrlString('https://solsynth.dev/terms');
},
), ),
onTap: () {
launchUrlString('https://solsynth.dev/terms');
},
), ),
), ),
], ),
), ],
), ),
onChanged: (value) {
setState(() => _isTermAccepted = value ?? false);
},
), ),
const Gap(16), onChanged: (value) {
Align( setState(() => _isTermAccepted = value ?? false);
alignment: Alignment.centerRight, },
child: TextButton( ),
onPressed: const Gap(16),
!_isTermAccepted ? null : () => _performAction(context), Align(
child: Row( alignment: Alignment.centerRight,
mainAxisSize: MainAxisSize.min, child: TextButton(
children: [ onPressed:
Text('next'.tr), !_isTermAccepted ? null : () => _performAction(context),
const Icon(Icons.chevron_right), child: Row(
], mainAxisSize: MainAxisSize.min,
), children: [
Text('next'.tr),
const Icon(Icons.chevron_right),
],
), ),
) ),
], )
), ],
).paddingAll(24), ).paddingAll(24),
); );
} }

View File

@ -11,6 +11,7 @@ import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/chat/call/call_controls.dart'; import 'package:solian/widgets/chat/call/call_controls.dart';
import 'package:solian/widgets/chat/call/call_participant.dart'; import 'package:solian/widgets/chat/call/call_participant.dart';
import 'package:livekit_client/livekit_client.dart' as livekit; import 'package:livekit_client/livekit_client.dart' as livekit;
import 'package:solian/widgets/root_container.dart';
class CallScreen extends StatefulWidget { class CallScreen extends StatefulWidget {
final bool hideAppBar; final bool hideAppBar;
@ -197,8 +198,7 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ChatCallProvider ctrl = Get.find(); final ChatCallProvider ctrl = Get.find();
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: Scaffold( child: Scaffold(
appBar: widget.hideAppBar appBar: widget.hideAppBar
? null ? null

View File

@ -3,6 +3,7 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/controllers/chat_events_controller.dart'; import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/models/call.dart'; import 'package:solian/models/call.dart';
@ -25,6 +26,7 @@ import 'package:solian/widgets/chat/chat_event_list.dart';
import 'package:solian/widgets/chat/chat_message_input.dart'; import 'package:solian/widgets/chat/chat_message_input.dart';
import 'package:solian/widgets/chat/chat_typing_indicator.dart'; import 'package:solian/widgets/chat/chat_typing_indicator.dart';
import 'package:solian/widgets/current_state_action.dart'; import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/root_container.dart';
class ChannelChatScreen extends StatefulWidget { class ChannelChatScreen extends StatefulWidget {
final String alias; final String alias;
@ -179,6 +181,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
} }
} }
late SharedPreferences _prefs;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -189,10 +193,13 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
_chatController = ChatEventController(); _chatController = ChatEventController();
_chatController.initialize(); _chatController.initialize();
_getOngoingCall(); SharedPreferences.getInstance().then((inst) {
_getChannel().then((_) { _prefs = inst;
_chatController.getInitialEvents(_channel!, widget.realm); _getOngoingCall();
_listenMessages(); _getChannel().then((_) {
_chatController.getInitialEvents(_channel!, widget.realm);
_listenMessages();
});
}); });
} }
@ -211,143 +218,149 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
); );
} }
return Scaffold( return ResponsiveRootContainer(
appBar: AppBar( child: Scaffold(
leading: AppBarLeadingButton.adaptive(context), appBar: AppBar(
title: AppBarTitle(title), leading: AppBarLeadingButton.adaptive(context),
centerTitle: false, title: AppBarTitle(title),
titleSpacing: AppTheme.titleSpacing(context), centerTitle: false,
toolbarHeight: AppTheme.toolbarHeight(context), titleSpacing: AppTheme.titleSpacing(context),
actions: [ toolbarHeight: AppTheme.toolbarHeight(context),
const BackgroundStateWidget(), actions: [
Builder(builder: (context) { const BackgroundStateWidget(),
if (_isBusy || _channel == null) return const SizedBox.shrink(); Builder(builder: (context) {
if (_isBusy || _channel == null) return const SizedBox.shrink();
return ChatCallButton( return ChatCallButton(
realm: _channel!.realm, realm: _channel!.realm,
channel: _channel!, channel: _channel!,
ongoingCall: _ongoingCall, ongoingCall: _ongoingCall,
); );
}), }),
IconButton( IconButton(
icon: const Icon(Icons.more_vert), icon: const Icon(Icons.more_vert),
onPressed: () { onPressed: () {
if (_channel == null) return; if (_channel == null) return;
AppRouter.instance AppRouter.instance
.pushNamed( .pushNamed(
'channelDetail', 'channelDetail',
pathParameters: {'alias': widget.alias}, pathParameters: {'alias': widget.alias},
queryParameters: {'realm': widget.realm}, queryParameters: {'realm': widget.realm},
extra: ChannelDetailArguments( extra: ChannelDetailArguments(
profile: _channelProfile!, profile: _channelProfile!,
channel: _channel!, channel: _channel!,
),
)
.then((value) {
if (value == false) AppRouter.instance.pop();
if (value != null) {
final resp = Channel.fromJson(value as Map<String, dynamic>);
_getChannel(alias: resp.alias);
}
});
},
),
SizedBox(
width: AppTheme.isLargeScreen(context) ? 8 : 16,
),
],
),
body: Builder(builder: (context) {
if (_isBusy || _channel == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Row(
children: [
Expanded(
child: Column(
children: [
if (_ongoingCall != null)
ChannelCallIndicator(
channel: _channel!,
ongoingCall: _ongoingCall!,
onJoin: () {
if (!AppTheme.isUltraLargeScreen(context)) {
final ChatCallProvider call = Get.find();
call.gotoScreen(context);
}
},
),
Expanded(
child: ChatEventList(
scope: widget.realm,
channel: _channel!,
chatController: _chatController,
onEdit: (item) {
setState(() => _messageToEditing = item);
},
onReply: (item) {
setState(() => _messageToReplying = item);
},
),
), ),
ClipRect( )
child: BackdropFilter( .then((value) {
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50), if (value == false) AppRouter.instance.pop();
child: SafeArea( if (value != null) {
child: Column( final resp =
children: [ Channel.fromJson(value as Map<String, dynamic>);
ChatTypingIndicator(users: _typingUsers), _getChannel(alias: resp.alias);
ChatMessageInput( }
edit: _messageToEditing, });
reply: _messageToReplying, },
realm: widget.realm, ),
placeholder: placeholder, SizedBox(
channel: _channel!, width: AppTheme.isLargeScreen(context) ? 8 : 16,
onSent: (Event item) { ),
setState(() { ],
_chatController.addPendingEvent(item); ),
}); body: Builder(builder: (context) {
}, if (_isBusy || _channel == null) {
onReset: () { return const Center(
setState(() { child: CircularProgressIndicator(),
_messageToReplying = null; );
_messageToEditing = null; }
});
}, return Row(
), children: [
], Expanded(
child: Column(
children: [
if (_ongoingCall != null)
ChannelCallIndicator(
channel: _channel!,
ongoingCall: _ongoingCall!,
onJoin: () {
if (!AppTheme.isUltraLargeScreen(context)) {
final ChatCallProvider call = Get.find();
call.gotoScreen(context);
}
},
),
Expanded(
child: ChatEventList(
noAnimated:
_prefs.getBool('non_animated_message_list') ??
false,
scope: widget.realm,
channel: _channel!,
chatController: _chatController,
onEdit: (item) {
setState(() => _messageToEditing = item);
},
onReply: (item) {
setState(() => _messageToReplying = item);
},
),
),
ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
child: SafeArea(
child: Column(
children: [
ChatTypingIndicator(users: _typingUsers),
ChatMessageInput(
edit: _messageToEditing,
reply: _messageToReplying,
realm: widget.realm,
placeholder: placeholder,
channel: _channel!,
onSent: (Event item) {
setState(() {
_chatController.addPendingEvent(item);
});
},
onReset: () {
setState(() {
_messageToReplying = null;
_messageToEditing = null;
});
},
),
],
),
), ),
), ),
), ),
), ],
], ),
), ),
), Obx(() {
Obx(() { final ChatCallProvider call = Get.find();
final ChatCallProvider call = Get.find(); if (call.isMounted.value &&
if (call.isMounted.value && AppTheme.isUltraLargeScreen(context)) {
AppTheme.isUltraLargeScreen(context)) { return const Expanded(
return const Expanded( child: Row(children: [
child: Row(children: [ VerticalDivider(width: 0.3, thickness: 0.3),
VerticalDivider(width: 0.3, thickness: 0.3), Expanded(
Expanded( child: CallScreen(
child: CallScreen( hideAppBar: true,
hideAppBar: true, isExpandable: true,
isExpandable: true, ),
), ),
), ]),
]), );
); }
} return const SizedBox.shrink();
return const SizedBox.shrink(); }),
}), ],
], );
); }),
}), ),
); );
} }

View File

@ -9,6 +9,7 @@ import 'package:solian/providers/content/channel.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_title.dart'; import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class ChannelOrganizeArguments { class ChannelOrganizeArguments {
@ -114,8 +115,7 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
), ),
]; ];
return Material( return ResponsiveRootContainer(
color: Theme.of(context).colorScheme.surface,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: AppBarTitle('channelOrganizing'.tr), title: AppBarTitle('channelOrganizing'.tr),

View File

@ -19,6 +19,7 @@ import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/channel/channel_list.dart'; import 'package:solian/widgets/channel/channel_list.dart';
import 'package:solian/widgets/chat/call/chat_call_indicator.dart'; import 'package:solian/widgets/chat/call/chat_call_indicator.dart';
import 'package:solian/widgets/current_state_action.dart'; import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sidebar/empty_placeholder.dart'; import 'package:solian/widgets/sidebar/empty_placeholder.dart';
class ChatScreen extends StatelessWidget { class ChatScreen extends StatelessWidget {
@ -26,9 +27,8 @@ class ChatScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return const ResponsiveRootContainer(
color: Theme.of(context).colorScheme.surface, child: ChatList(),
child: const ChatList(),
); );
} }
} }
@ -40,8 +40,7 @@ class ChatListShell extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: Row( child: Row(
children: [ children: [
const SizedBox( const SizedBox(
@ -141,181 +140,186 @@ class _ChatListState extends State<ChatList> {
return Obx( return Obx(
() => DefaultTabController( () => DefaultTabController(
length: 2 + realms.availableRealms.length, length: 2 + realms.availableRealms.length,
child: Scaffold( child: ResponsiveRootContainer(
appBar: AppBar( child: Scaffold(
leading: Obx(() { appBar: AppBar(
final adaptive = AppBarLeadingButton.adaptive(context); leading: Obx(() {
if (adaptive != null) return adaptive; final adaptive = AppBarLeadingButton.adaptive(context);
if (_channels.isLoading.value) { if (adaptive != null) return adaptive;
return const CircularProgressIndicator( if (_channels.isLoading.value) {
strokeWidth: 3, return const CircularProgressIndicator(
).paddingAll(18); strokeWidth: 3,
} ).paddingAll(18);
return const SizedBox.shrink(); }
}), return const SizedBox.shrink();
title: AppBarTitle('chat'.tr), }),
centerTitle: true, title: AppBarTitle('chat'.tr),
toolbarHeight: AppTheme.toolbarHeight(context), centerTitle: true,
actions: [ toolbarHeight: AppTheme.toolbarHeight(context),
const BackgroundStateWidget(), actions: [
const NotificationButton(), const BackgroundStateWidget(),
PopupMenuButton( const NotificationButton(),
icon: const Icon(Icons.add_circle), PopupMenuButton(
itemBuilder: (BuildContext context) => [ icon: const Icon(Icons.add_circle),
PopupMenuItem( itemBuilder: (BuildContext context) => [
child: ListTile( PopupMenuItem(
title: Text('channelOrganizeCommon'.tr), child: ListTile(
leading: const Icon(Icons.tag), title: Text('channelOrganizeCommon'.tr),
contentPadding: const EdgeInsets.symmetric(horizontal: 8), leading: const Icon(Icons.tag),
contentPadding:
const EdgeInsets.symmetric(horizontal: 8),
),
onTap: () {
AppRouter.instance.pushNamed('channelOrganizing').then(
(value) {
if (value != null) {
_loadAllChannels();
}
},
);
},
), ),
onTap: () { PopupMenuItem(
AppRouter.instance.pushNamed('channelOrganizing').then( child: ListTile(
(value) { title: Text('channelOrganizeDirect'.tr),
if (value != null) { leading: const FaIcon(
FontAwesomeIcons.userGroup,
size: 16,
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 8),
),
onTap: () {
final ChannelProvider channels = Get.find();
channels
.createDirectChannel(context, 'global')
.then((resp) {
if (resp != null) {
_loadAllChannels(); _loadAllChannels();
} }
}, }).catchError((e) {
); context.showErrorDialog(e);
}, });
), },
PopupMenuItem( ),
child: ListTile( ],
title: Text('channelOrganizeDirect'.tr), ),
leading: const FaIcon( SizedBox(
FontAwesomeIcons.userGroup, width: AppTheme.isLargeScreen(context) ? 8 : 16,
size: 16, ),
), ],
contentPadding: const EdgeInsets.symmetric(horizontal: 8), bottom: TabBar(
isScrollable: true,
dividerHeight: 0.3,
tabAlignment: TabAlignment.startOffset,
tabs: [
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 14,
backgroundColor:
Theme.of(context).colorScheme.primary,
child: const Icon(
Icons.forum,
size: 16,
color: Colors.white,
),
),
const Gap(8),
Text('all'.tr),
],
), ),
onTap: () {
final ChannelProvider channels = Get.find();
channels
.createDirectChannel(context, 'global')
.then((resp) {
if (resp != null) {
_loadAllChannels();
}
}).catchError((e) {
context.showErrorDialog(e);
});
},
), ),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const CircleAvatar(
radius: 14,
child: Icon(
Icons.chat_bubble,
size: 16,
),
),
const Gap(8),
Text('channelTypeDirect'.tr),
],
),
),
...realms.availableRealms.map((x) => Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AccountAvatar(
content: x.avatar,
radius: 14,
fallbackWidget: const Icon(
Icons.workspaces,
size: 16,
),
),
const Gap(8),
Text(x.name),
],
),
)),
], ],
), ),
SizedBox(
width: AppTheme.isLargeScreen(context) ? 8 : 16,
),
],
bottom: TabBar(
isScrollable: true,
dividerHeight: 0.3,
tabAlignment: TabAlignment.startOffset,
tabs: [
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 14,
backgroundColor: Theme.of(context).colorScheme.primary,
child: const Icon(
Icons.forum,
size: 16,
color: Colors.white,
),
),
const Gap(8),
Text('all'.tr),
],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const CircleAvatar(
radius: 14,
child: Icon(
Icons.chat_bubble,
size: 16,
),
),
const Gap(8),
Text('channelTypeDirect'.tr),
],
),
),
...realms.availableRealms.map((x) => Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AccountAvatar(
content: x.avatar,
radius: 14,
fallbackWidget: const Icon(
Icons.workspaces,
size: 16,
),
),
const Gap(8),
Text(x.name),
],
),
)),
],
), ),
), body: Obx(() {
body: Obx(() { if (auth.isAuthorized.isFalse) {
if (auth.isAuthorized.isFalse) { return SigninRequiredOverlay(
return SigninRequiredOverlay( onDone: () => _loadAllChannels(),
onDone: () => _loadAllChannels(), );
); }
}
final selfId = auth.userProfile.value!['id']; final selfId = auth.userProfile.value!['id'];
return Column( return Column(
children: [ children: [
const ChatCallCurrentIndicator(), const ChatCallCurrentIndicator(),
Expanded( Expanded(
child: TabBarView( child: TabBarView(
children: [ children: [
RefreshIndicator( RefreshIndicator(
onRefresh: _loadNormalChannels, onRefresh: _loadNormalChannels,
child: ChannelListWidget(
channels: _sortChannels([
..._normalChannels,
..._directChannels,
..._realmChannels.values.expand((x) => x),
]),
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
),
RefreshIndicator(
onRefresh: _loadDirectChannels,
child: ChannelListWidget(
channels: _directChannels,
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
),
...realms.availableRealms.map(
(x) => RefreshIndicator(
onRefresh: () => _loadRealmChannels(x.alias),
child: ChannelListWidget( child: ChannelListWidget(
channels: _realmChannels[x.alias] ?? [], channels: _sortChannels([
..._normalChannels,
..._directChannels,
..._realmChannels.values.expand((x) => x),
]),
selfId: selfId, selfId: selfId,
useReplace: AppTheme.isLargeScreen(context), useReplace: AppTheme.isLargeScreen(context),
), ),
), ),
), RefreshIndicator(
], onRefresh: _loadDirectChannels,
child: ChannelListWidget(
channels: _directChannels,
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
),
...realms.availableRealms.map(
(x) => RefreshIndicator(
onRefresh: () => _loadRealmChannels(x.alias),
child: ChannelListWidget(
channels: _realmChannels[x.alias] ?? [],
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
),
),
],
),
), ),
), ],
], );
); }),
}), ),
), ),
), ),
); );

View File

@ -89,13 +89,17 @@ class _DashboardScreenState extends State<DashboardScreen> {
try { try {
_signRecord = await _dailySign.getToday(); _signRecord = await _dailySign.getToday();
_dailySign.listLastRecord(14).then((value) { _dailySign.listLastRecord(14).then((value) {
setState(() => _signRecordHistory = value); if (mounted) {
setState(() => _signRecordHistory = value);
}
}); });
} catch (e) { } catch (e) {
context.showErrorDialog(e); context.showErrorDialog(e);
} }
setState(() => _signingDaily = false); if (mounted) {
setState(() => _signingDaily = false);
}
} }
Future<void> _signDaily() async { Future<void> _signDaily() async {
@ -147,7 +151,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
), ),
Text(DateFormat('yyyy/MM/dd').format(DateTime.now().toUtc())), Text(DateFormat('yyyy/MM/dd').format(DateTime.now().toUtc())),
], ],
).paddingOnly(top: 8, left: 18, right: 18, bottom: 12), ).paddingOnly(top: 16, left: 18, right: 18, bottom: 12),
Card( Card(
child: Column( child: Column(
children: [ children: [
@ -354,7 +358,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
IconButton( IconButton(
icon: const Icon(Icons.arrow_forward), icon: const Icon(Icons.arrow_forward),
onPressed: () { onPressed: () {
AppRouter.instance.goNamed('feed'); AppRouter.instance.goNamed('explore');
}, },
), ),
], ],

View File

@ -15,6 +15,7 @@ import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/navigation/realm_switcher.dart'; import 'package:solian/widgets/navigation/realm_switcher.dart';
import 'package:solian/widgets/posts/post_shuffle_swiper.dart'; import 'package:solian/widgets/posts/post_shuffle_swiper.dart';
import 'package:solian/widgets/posts/post_warped_list.dart'; import 'package:solian/widgets/posts/post_warped_list.dart';
import 'package:solian/widgets/root_container.dart';
class ExploreScreen extends StatefulWidget { class ExploreScreen extends StatefulWidget {
const ExploreScreen({super.key}); const ExploreScreen({super.key});
@ -56,8 +57,7 @@ class _ExploreScreenState extends State<ExploreScreen>
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: Scaffold( child: Scaffold(
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add), child: const Icon(Icons.add),

View File

@ -9,6 +9,7 @@ import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/app_bar_title.dart'; import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/posts/post_action.dart'; import 'package:solian/widgets/posts/post_action.dart';
import 'package:solian/widgets/posts/post_owned_list.dart'; import 'package:solian/widgets/posts/post_owned_list.dart';
import 'package:solian/widgets/root_container.dart';
class DraftBoxScreen extends StatefulWidget { class DraftBoxScreen extends StatefulWidget {
const DraftBoxScreen({super.key}); const DraftBoxScreen({super.key});
@ -54,8 +55,7 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),

View File

@ -47,51 +47,48 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return FutureBuilder(
color: Theme.of(context).colorScheme.surface, future: getDetail(),
child: FutureBuilder( builder: (context, snapshot) {
future: getDetail(), if (!snapshot.hasData || snapshot.data == null) {
builder: (context, snapshot) { return const Center(
if (!snapshot.hasData || snapshot.data == null) { child: CircularProgressIndicator(),
return const Center(
child: CircularProgressIndicator(),
);
}
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: PostItem(
item: item!,
isClickable: false,
isOverrideEmbedClickable: true,
isFullDate: true,
isFullContent: true,
isShowReply: false,
isContentSelectable: true,
),
),
SliverToBoxAdapter(
child: const Divider(thickness: 0.3, height: 1)
.paddingOnly(top: 4),
),
SliverToBoxAdapter(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'postReplies'.tr,
style: Theme.of(context).textTheme.headlineSmall,
).paddingOnly(left: 24, right: 24, top: 16),
),
),
PostReplyList(item: item!),
SliverToBoxAdapter(
child: SizedBox(height: MediaQuery.of(context).padding.bottom),
),
],
); );
}, }
),
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: PostItem(
item: item!,
isClickable: false,
isOverrideEmbedClickable: true,
isFullDate: true,
isFullContent: true,
isShowReply: false,
isContentSelectable: true,
),
),
SliverToBoxAdapter(
child:
const Divider(thickness: 0.3, height: 1).paddingOnly(top: 4),
),
SliverToBoxAdapter(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'postReplies'.tr,
style: Theme.of(context).textTheme.headlineSmall,
).paddingOnly(left: 24, right: 24, top: 16),
),
),
PostReplyList(item: item!),
SliverToBoxAdapter(
child: SizedBox(height: MediaQuery.of(context).padding.bottom),
),
],
);
},
); );
} }
} }

View File

@ -19,6 +19,7 @@ import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/markdown_text_content.dart'; import 'package:solian/widgets/markdown_text_content.dart';
import 'package:solian/widgets/posts/post_item.dart'; import 'package:solian/widgets/posts/post_item.dart';
import 'package:badges/badges.dart' as badges; import 'package:badges/badges.dart' as badges;
import 'package:solian/widgets/root_container.dart';
class PostPublishArguments { class PostPublishArguments {
final Post? edit; final Post? edit;
@ -151,8 +152,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
) )
]; ];
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),

View File

@ -15,6 +15,7 @@ import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/app_bar_title.dart'; import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/auto_cache_image.dart'; import 'package:solian/widgets/auto_cache_image.dart';
import 'package:solian/widgets/current_state_action.dart'; import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart'; import 'package:solian/widgets/sized_container.dart';
class RealmListScreen extends StatefulWidget { class RealmListScreen extends StatefulWidget {
@ -58,8 +59,7 @@ class _RealmListScreenState extends State<RealmListScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
@ -99,6 +99,7 @@ class _RealmListScreenState extends State<RealmListScreen> {
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () => _getRealms(), onRefresh: () => _getRealms(),
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 16),
itemCount: _realms.length, itemCount: _realms.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final element = _realms[index]; final element = _realms[index];

View File

@ -7,6 +7,7 @@ import 'package:solian/router.dart';
import 'package:solian/screens/realms/realm_organize.dart'; import 'package:solian/screens/realms/realm_organize.dart';
import 'package:solian/widgets/realms/realm_deletion.dart'; import 'package:solian/widgets/realms/realm_deletion.dart';
import 'package:solian/widgets/realms/realm_member.dart'; import 'package:solian/widgets/realms/realm_member.dart';
import 'package:solian/widgets/root_container.dart';
class RealmDetailScreen extends StatefulWidget { class RealmDetailScreen extends StatefulWidget {
final String alias; final String alias;
@ -86,61 +87,63 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
), ),
]; ];
return Column( return RootContainer(
children: [ child: Column(
Padding( children: [
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), Padding(
child: Row( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [ child: Row(
const CircleAvatar( children: [
radius: 28, const CircleAvatar(
backgroundColor: Colors.teal, radius: 28,
child: Icon(Icons.group, color: Colors.white), backgroundColor: Colors.teal,
), child: Icon(Icons.group, color: Colors.white),
const Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.realm.name,
style: Theme.of(context).textTheme.bodyLarge),
Text(widget.realm.description,
style: Theme.of(context).textTheme.bodySmall),
Text(
'#${widget.realm.id.toString().padLeft(8, '0')} · ${widget.realm.alias}',
style: const TextStyle(fontSize: 11),
),
],
), ),
) const Gap(16),
], Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.realm.name,
style: Theme.of(context).textTheme.bodyLarge),
Text(widget.realm.description,
style: Theme.of(context).textTheme.bodySmall),
Text(
'#${widget.realm.id.toString().padLeft(8, '0')} · ${widget.realm.alias}',
style: const TextStyle(fontSize: 11),
),
],
),
)
],
),
), ),
), const Divider(thickness: 0.3),
const Divider(thickness: 0.3), Expanded(
Expanded( child: ListView(
child: ListView( children: [
children: [ ListTile(
ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24),
contentPadding: const EdgeInsets.symmetric(horizontal: 24), leading: const Icon(Icons.supervisor_account),
leading: const Icon(Icons.supervisor_account), trailing: const Icon(Icons.chevron_right),
trailing: const Icon(Icons.chevron_right), title: Text('realmMembers'.tr),
title: Text('realmMembers'.tr), onTap: () => showMemberList(),
onTap: () => showMemberList(), ),
), ...(_isOwned ? ownerActions : List.empty()),
...(_isOwned ? ownerActions : List.empty()), const Divider(thickness: 0.3),
const Divider(thickness: 0.3), ListTile(
ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24),
contentPadding: const EdgeInsets.symmetric(horizontal: 24), leading: _isOwned
leading: _isOwned ? const Icon(Icons.delete)
? const Icon(Icons.delete) : const Icon(Icons.exit_to_app),
: const Icon(Icons.exit_to_app), title: Text(_isOwned ? 'delete'.tr : 'leave'.tr),
title: Text(_isOwned ? 'delete'.tr : 'leave'.tr), onTap: () => promptLeaveChannel(),
onTap: () => promptLeaveChannel(), ),
), ],
], ),
), ),
), ],
], ),
); );
} }
} }

View File

@ -13,6 +13,7 @@ import 'package:solian/router.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_leading.dart'; import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/app_bar_title.dart'; import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class RealmOrganizeArguments { class RealmOrganizeArguments {
@ -189,8 +190,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),

View File

@ -16,6 +16,7 @@ import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_leading.dart'; import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/channel/channel_list.dart'; import 'package:solian/widgets/channel/channel_list.dart';
import 'package:solian/widgets/posts/post_list.dart'; import 'package:solian/widgets/posts/post_list.dart';
import 'package:solian/widgets/root_container.dart';
class RealmViewScreen extends StatefulWidget { class RealmViewScreen extends StatefulWidget {
final String alias; final String alias;
@ -86,8 +87,7 @@ class _RealmViewScreenState extends State<RealmViewScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return RootContainer(
color: Theme.of(context).colorScheme.surface,
child: DefaultTabController( child: DefaultTabController(
length: 2, length: 2,
child: NestedScrollView( child: NestedScrollView(

View File

@ -1,16 +1,23 @@
import 'dart:convert';
import 'dart:io';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:in_app_review/in_app_review.dart'; import 'package:in_app_review/in_app_review.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/exceptions/request.dart'; import 'package:solian/exceptions/request.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/models/theme.dart';
import 'package:solian/platform.dart'; import 'package:solian/platform.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/database/database.dart'; import 'package:solian/providers/database/database.dart';
import 'package:solian/providers/theme_switcher.dart'; import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/reports/abuse_report.dart'; import 'package:solian/widgets/reports/abuse_report.dart';
class SettingScreen extends StatefulWidget { class SettingScreen extends StatefulWidget {
@ -22,6 +29,7 @@ class SettingScreen extends StatefulWidget {
class _SettingScreenState extends State<SettingScreen> { class _SettingScreenState extends State<SettingScreen> {
SharedPreferences? _prefs; SharedPreferences? _prefs;
String _docBasepath = '/';
Widget _buildCaptionHeader(String title) { Widget _buildCaptionHeader(String title) {
return Container( return Container(
@ -32,39 +40,38 @@ class _SettingScreenState extends State<SettingScreen> {
); );
} }
Widget _buildThemeColorButton(String label, Color color) { static final List<SolianThemeData> _presentTheme = [
return IconButton( SolianThemeData(
icon: Icon(Icons.circle, color: color), id: 'themeColorRed',
tooltip: label, seedColor: const Color.fromRGBO(154, 98, 91, 1),
onPressed: () { ),
context.read<ThemeSwitcher>().setTheme( SolianThemeData(
AppTheme.build( id: 'themeColorBlue',
Brightness.light, seedColor: const Color.fromRGBO(103, 96, 193, 1),
seedColor: color, ),
), SolianThemeData(
AppTheme.build( id: 'themeColorMiku',
Brightness.dark, seedColor: const Color.fromRGBO(56, 120, 126, 1),
seedColor: color, ),
), SolianThemeData(
); id: 'themeColorKagamine',
_prefs?.setInt('global_theme_color', color.value); seedColor: const Color.fromRGBO(244, 183, 63, 1),
context.clearSnackbar(); ),
context.showSnackbar('themeColorApplied'.tr); SolianThemeData(
}, id: 'themeColorLuka',
); seedColor: const Color.fromRGBO(243, 174, 218, 1),
} ),
static final List<(String, Color)> _presentTheme = [
('themeColorRed', const Color.fromRGBO(154, 98, 91, 1)),
('themeColorBlue', const Color.fromRGBO(103, 96, 193, 1)),
('themeColorMiku', const Color.fromRGBO(56, 120, 126, 1)),
('themeColorKagamine', const Color.fromRGBO(244, 183, 63, 1)),
('themeColorLuka', const Color.fromRGBO(243, 174, 218, 1)),
]; ];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
getApplicationDocumentsDirectory().then((dir) {
_docBasepath = dir.path;
if (mounted) {
setState(() {});
}
});
SharedPreferences.getInstance().then((inst) { SharedPreferences.getInstance().then((inst) {
_prefs = inst; _prefs = inst;
if (mounted) { if (mounted) {
@ -75,163 +82,258 @@ class _SettingScreenState extends State<SettingScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return ListView(
color: Theme.of(context).colorScheme.surface, children: [
child: ListView( _buildCaptionHeader('theme'.tr),
children: [ ListTile(
_buildCaptionHeader('themeColor'.tr), leading: const Icon(Icons.palette),
SizedBox( contentPadding: const EdgeInsets.symmetric(horizontal: 22),
height: 56, title: Text('globalTheme'.tr),
child: ListView( trailing: DropdownButtonHideUnderline(
scrollDirection: Axis.horizontal, child: DropdownButton2<SolianThemeData>(
children: _presentTheme isExpanded: true,
.map((x) => _buildThemeColorButton(x.$1, x.$2)) hint: Text(
.toList(), 'theme'.tr,
).paddingSymmetric(horizontal: 12, vertical: 8), style: TextStyle(
), fontSize: 14,
_buildCaptionHeader('notification'.tr), color: Theme.of(context).hintColor,
Tooltip( ),
message: 'settingsNotificationBgServiceDesc'.tr,
child: CheckboxListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
secondary: const Icon(Icons.system_security_update_warning),
enabled: PlatformInfo.isAndroid,
title: Text('settingsNotificationBgService'.tr),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('holdToSeeDetail'.tr),
Text(
'needRestartToApply'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
)
],
), ),
value: items: _presentTheme
_prefs?.getBool('service_background_notification') ?? false, .map((SolianThemeData item) =>
onChanged: (value) { DropdownMenuItem<SolianThemeData>(
_prefs value: item,
?.setBool('service_background_notification', value ?? false) child: Row(
.then((_) { crossAxisAlignment: CrossAxisAlignment.center,
setState(() {}); children: [
}); Icon(Icons.circle, color: item.seedColor),
const Gap(8),
Expanded(
child: Text(
item.id.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 14,
),
),
),
],
),
))
.toList(),
value: (_prefs?.containsKey('global_theme') ?? false)
? SolianThemeData.fromJson(
jsonDecode(_prefs!.getString('global_theme')!),
)
: null,
onChanged: (SolianThemeData? value) {
context.read<ThemeSwitcher>().setThemeData(value);
setState(() {});
}, },
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.symmetric(horizontal: 8),
height: 40,
width: 140,
),
menuItemStyleData: const MenuItemStyleData(
height: 40,
),
), ),
), ),
_buildCaptionHeader('update'.tr), ),
CheckboxListTile( CheckboxListTile(
secondary: const Icon(Icons.military_tech),
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
title: Text('agedTheme'.tr),
subtitle: Text('agedThemeDesc'.tr),
value: _prefs?.getBool('aged_theme') ?? false,
onChanged: (value) {
if (value != null) {
context.read<ThemeSwitcher>().setAgedTheme(value);
}
setState(() {});
},
),
if (!PlatformInfo.isWeb)
ListTile(
leading: const Icon(Icons.wallpaper),
contentPadding: const EdgeInsets.only(left: 22, right: 31),
title: Text('appBackgroundImage'.tr),
subtitle: Text('appBackgroundImageDesc'.tr),
trailing: File('$_docBasepath/app_background_image').existsSync()
? const Icon(Icons.check_box)
: const Icon(Icons.check_box_outline_blank),
onTap: () async {
if (File('$_docBasepath/app_background_image').existsSync()) {
File('$_docBasepath/app_background_image').deleteSync();
} else {
final image = await ImagePicker().pickImage(
source: ImageSource.gallery,
);
if (image == null) return;
await File(image.path)
.copy('$_docBasepath/app_background_image');
}
setState(() {});
},
),
_buildCaptionHeader('notification'.tr),
Tooltip(
message: 'settingsNotificationBgServiceDesc'.tr,
child: CheckboxListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 22), contentPadding: const EdgeInsets.symmetric(horizontal: 22),
secondary: const Icon(Icons.sync_alt), secondary: const Icon(Icons.system_security_update_warning),
title: Text('updateCheckStrictly'.tr), enabled: PlatformInfo.isAndroid,
subtitle: Text('updateCheckStrictlyDesc'.tr), title: Text('settingsNotificationBgService'.tr),
value: _prefs?.getBool('check_update_strictly') ?? false, subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('holdToSeeDetail'.tr),
Text(
'needRestartToApply'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
)
],
),
value: _prefs?.getBool('service_background_notification') ?? false,
onChanged: (value) { onChanged: (value) {
_prefs _prefs
?.setBool('check_update_strictly', value ?? false) ?.setBool('service_background_notification', value ?? false)
.then((_) { .then((_) {
setState(() {}); setState(() {});
}); });
}, },
), ),
Obx(() { ),
final AuthProvider auth = Get.find<AuthProvider>(); _buildCaptionHeader('update'.tr),
if (!auth.isAuthorized.value) return const SizedBox.shrink(); CheckboxListTile(
return Column( contentPadding: const EdgeInsets.symmetric(horizontal: 22),
children: [ secondary: const Icon(Icons.sync_alt),
_buildCaptionHeader('account'.tr), title: Text('updateCheckStrictly'.tr),
ListTile( subtitle: Text('updateCheckStrictlyDesc'.tr),
leading: const Icon(Icons.flag), value: _prefs?.getBool('check_update_strictly') ?? false,
trailing: const Icon(Icons.chevron_right), onChanged: (value) {
contentPadding: const EdgeInsets.symmetric(horizontal: 22), _prefs?.setBool('check_update_strictly', value ?? false).then((_) {
title: Text('reportAbuse'.tr), setState(() {});
subtitle: Text('reportAbuseDesc'.tr), });
onTap: () { },
showDialog( ),
context: context, Obx(() {
builder: (context) => const AbuseReportDialog(), final AuthProvider auth = Get.find<AuthProvider>();
); if (!auth.isAuthorized.value) return const SizedBox.shrink();
}, return Column(
), children: [
ListTile( _buildCaptionHeader('account'.tr),
leading: const Icon(Icons.person_remove), ListTile(
trailing: const Icon(Icons.chevron_right), leading: const Icon(Icons.flag),
contentPadding: const EdgeInsets.symmetric(horizontal: 22), trailing: const Icon(Icons.chevron_right),
title: Text('accountDeletion'.tr), contentPadding: const EdgeInsets.symmetric(horizontal: 22),
subtitle: Text('accountDeletionDesc'.tr), title: Text('reportAbuse'.tr),
onTap: () { subtitle: Text('reportAbuseDesc'.tr),
context onTap: () {
.showSlideToConfirmDialog( showDialog(
'accountDeletionConfirm'.tr, context: context,
'accountDeletionConfirmDesc'.trParams({ builder: (context) => const AbuseReportDialog(),
'account': '@${auth.userProfile.value!['name']}', );
}), },
) ),
.then((value) async { ListTile(
if (value != true) return; leading: const Icon(Icons.person_remove),
final client = await auth.configureClient('id'); trailing: const Icon(Icons.chevron_right),
final resp = await client.post('/users/me/deletion', {}); contentPadding: const EdgeInsets.symmetric(horizontal: 22),
if (resp.statusCode != 200) { title: Text('accountDeletion'.tr),
context.showErrorDialog(RequestException(resp)); subtitle: Text('accountDeletionDesc'.tr),
} else { onTap: () {
context.showSnackbar('accountDeletionRequested'.tr); context
} .showSlideToConfirmDialog(
}); 'accountDeletionConfirm'.tr,
}, 'accountDeletionConfirmDesc'.trParams({
), 'account': '@${auth.userProfile.value!['name']}',
], }),
); )
}), .then((value) async {
_buildCaptionHeader('more'.tr), if (value != true) return;
ListTile( final client = await auth.configureClient('id');
leading: const Icon(Icons.delete_sweep), final resp = await client.post('/users/me/deletion', {});
trailing: const Icon(Icons.chevron_right), if (resp.statusCode != 200) {
subtitle: FutureBuilder( context.showErrorDialog(RequestException(resp));
future: AppDatabase.getDatabaseSize(), } else {
builder: (context, snapshot) { context.showSnackbar('accountDeletionRequested'.tr);
if (!snapshot.hasData) { }
return Text('localDatabaseSize'.trParams( });
{'size': 'unknown'.tr}, },
)); ),
} ],
);
}),
_buildCaptionHeader('performance'.tr),
CheckboxListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
secondary: const Icon(Icons.message),
title: Text('animatedMessageList'.tr),
subtitle: Text('animatedMessageListDesc'.tr),
value: _prefs?.getBool('non_animated_message_list') ?? false,
onChanged: (value) {
_prefs
?.setBool('non_animated_message_list', value ?? false)
.then((_) {
setState(() {});
});
},
),
_buildCaptionHeader('more'.tr),
ListTile(
leading: const Icon(Icons.delete_sweep),
trailing: const Icon(Icons.chevron_right),
subtitle: FutureBuilder(
future: AppDatabase.getDatabaseSize(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('localDatabaseSize'.trParams( return Text('localDatabaseSize'.trParams(
{'size': snapshot.data!.formatBytes()}, {'size': 'unknown'.tr},
)); ));
}, }
), return Text('localDatabaseSize'.trParams(
contentPadding: const EdgeInsets.symmetric(horizontal: 22), {'size': snapshot.data!.formatBytes()},
title: Text('localDatabaseWipe'.tr), ));
onTap: () {
AppDatabase.removeDatabase().then((_) {
setState(() {});
});
}, },
), ),
if (PlatformInfo.canRateTheApp) contentPadding: const EdgeInsets.symmetric(horizontal: 22),
ListTile( title: Text('localDatabaseWipe'.tr),
leading: const Icon(Icons.star), onTap: () {
trailing: const Icon(Icons.chevron_right), AppDatabase.removeDatabase().then((_) {
contentPadding: const EdgeInsets.symmetric(horizontal: 22), setState(() {});
title: Text('rateTheApp'.tr), });
subtitle: Text('rateTheAppDesc'.tr), },
onTap: () { ),
final inAppReview = InAppReview.instance; if (PlatformInfo.canRateTheApp)
inAppReview.openStoreListing(
appStoreId: '6499032345',
);
},
),
ListTile( ListTile(
leading: const Icon(Icons.info_outline), leading: const Icon(Icons.star),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
contentPadding: const EdgeInsets.symmetric(horizontal: 22), contentPadding: const EdgeInsets.symmetric(horizontal: 22),
title: Text('about'.tr), title: Text('rateTheApp'.tr),
subtitle: Text('rateTheAppDesc'.tr),
onTap: () { onTap: () {
AppRouter.instance.pushNamed('about'); final inAppReview = InAppReview.instance;
inAppReview.openStoreListing(
appStoreId: '6499032345',
);
}, },
), ),
], ListTile(
), leading: const Icon(Icons.info_outline),
trailing: const Icon(Icons.chevron_right),
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
title: Text('about'.tr),
onTap: () {
AppRouter.instance.pushNamed('about');
},
),
],
); );
} }
} }

View File

@ -5,10 +5,12 @@ import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_title.dart'; import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/app_bar_leading.dart'; import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/current_state_action.dart'; import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/root_container.dart';
class TitleShell extends StatelessWidget { class TitleShell extends StatelessWidget {
final bool showAppBar; final bool showAppBar;
final bool isCenteredTitle; final bool isCenteredTitle;
final bool isResponsive;
final String? title; final String? title;
final GoRouterState? state; final GoRouterState? state;
final Widget child; final Widget child;
@ -20,13 +22,14 @@ class TitleShell extends StatelessWidget {
this.state, this.state,
this.showAppBar = true, this.showAppBar = true,
this.isCenteredTitle = false, this.isCenteredTitle = false,
this.isResponsive = false,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(state != null || title != null); assert(state != null || title != null);
return Scaffold( final widget = Scaffold(
appBar: showAppBar appBar: showAppBar
? AppBar( ? AppBar(
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
@ -45,5 +48,11 @@ class TitleShell extends StatelessWidget {
: null, : null,
body: child, body: child,
); );
if (isResponsive) {
return ResponsiveRootContainer(child: widget);
} else {
return RootContainer(child: widget);
}
} }
} }

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:solian/models/theme.dart';
import 'package:solian/platform.dart'; import 'package:solian/platform.dart';
abstract class AppTheme { abstract class AppTheme {
@ -41,6 +42,10 @@ abstract class AppTheme {
snackBarTheme: const SnackBarThemeData( snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
), ),
scaffoldBackgroundColor: Colors.transparent,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
),
fontFamily: 'Comfortaa', fontFamily: 'Comfortaa',
fontFamilyFallback: [ fontFamilyFallback: [
'NotoSansSC', 'NotoSansSC',
@ -55,4 +60,37 @@ abstract class AppTheme {
), ),
); );
} }
static ThemeData buildFromData(
Brightness brightness,
SolianThemeData data, {
bool useMaterial3 = true,
}) {
return ThemeData(
brightness: brightness,
useMaterial3: useMaterial3,
colorScheme: ColorScheme.fromSeed(
brightness: brightness,
seedColor: data.seedColor,
),
snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating,
),
scaffoldBackgroundColor: Colors.transparent,
appBarTheme: const AppBarTheme(backgroundColor: Colors.transparent),
fontFamily: data.fontFamily ?? 'Comfortaa',
fontFamilyFallback: data.fontFamilyFallback ??
[
'NotoSansSC',
'NotoSansHK',
'NotoSansJP',
if (PlatformInfo.isWeb) 'NotoSansEmoji',
],
typography: Typography.material2021(
colorScheme: brightness == Brightness.light
? const ColorScheme.light()
: const ColorScheme.dark(),
),
);
}
} }

View File

@ -118,7 +118,7 @@ class _AccountProfilePopupState extends State<AccountProfilePopup> {
const VisualDensity(horizontal: -4, vertical: -2), const VisualDensity(horizontal: -4, vertical: -2),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
AppRouter.instance.goNamed( AppRouter.instance.pushNamed(
'accountProfilePage', 'accountProfilePage',
pathParameters: {'name': _userinfo!.name}, pathParameters: {'name': _userinfo!.name},
); );

View File

@ -1,28 +1,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:solian/shells/root_shell.dart';
class AppBarLeadingButton extends StatelessWidget { class AppBarLeadingButton extends StatelessWidget {
const AppBarLeadingButton({super.key}); final bool forceBack;
static Widget? adaptive(BuildContext context) { const AppBarLeadingButton({super.key, this.forceBack = false});
final hasContent =
Navigator.canPop(context) || rootScaffoldKey.currentState!.hasDrawer; static Widget? adaptive(BuildContext context, {bool forceBack = false}) {
return hasContent ? const AppBarLeadingButton() : null; final hasContent = Navigator.canPop(context) || forceBack;
return hasContent ? AppBarLeadingButton(forceBack: forceBack) : null;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (Navigator.canPop(context)) { if (Navigator.canPop(context) || forceBack) {
return BackButton( return BackButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
); );
} }
if (rootScaffoldKey.currentState!.hasDrawer) { return const SizedBox.shrink();
return DrawerButton(
onPressed: () => rootScaffoldKey.currentState!.openDrawer(),
);
} else {
return const SizedBox.shrink();
}
} }
} }

View File

@ -177,9 +177,6 @@ class _AttachmentListState extends State<AttachmentList> {
if (element == null) return const SizedBox.shrink(); if (element == null) return const SizedBox.shrink();
double ratio = element.metadata?['ratio']?.toDouble() ?? 16 / 9; double ratio = element.metadata?['ratio']?.toDouble() ?? 16 / 9;
return Container( return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: widget.columnMaxWidth, maxWidth: widget.columnMaxWidth,
maxHeight: 640, maxHeight: 640,
@ -247,7 +244,7 @@ class _AttachmentListState extends State<AttachmentList> {
maxHeight: widget.flatMaxHeight, maxHeight: widget.flatMaxHeight,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh, color: Colors.transparent,
border: Border.symmetric( border: Border.symmetric(
horizontal: BorderSide( horizontal: BorderSide(
width: 0.3, width: 0.3,
@ -257,6 +254,7 @@ class _AttachmentListState extends State<AttachmentList> {
), ),
child: CarouselSlider.builder( child: CarouselSlider.builder(
options: CarouselOptions( options: CarouselOptions(
animateToClosest: true,
aspectRatio: _aspectRatio, aspectRatio: _aspectRatio,
viewportFraction: viewportFraction:
widget.viewport ?? (widget.attachmentsId.length > 1 ? 0.95 : 1), widget.viewport ?? (widget.attachmentsId.length > 1 ? 0.95 : 1),
@ -319,6 +317,7 @@ class AttachmentListEntry extends StatelessWidget {
width: width ?? MediaQuery.of(context).size.width, width: width ?? MediaQuery.of(context).size.width,
height: height, height: height,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.transparent,
border: showBorder border: showBorder
? Border.symmetric( ? Border.symmetric(
vertical: BorderSide( vertical: BorderSide(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/controllers/chat_events_controller.dart'; import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/models/channel.dart'; import 'package:solian/models/channel.dart';
@ -9,6 +10,7 @@ import 'package:solian/widgets/chat/chat_event_action.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart';
class ChatEventList extends StatelessWidget { class ChatEventList extends StatelessWidget {
final bool noAnimated;
final String scope; final String scope;
final Channel channel; final Channel channel;
final ChatEventController chatController; final ChatEventController chatController;
@ -23,6 +25,7 @@ class ChatEventList extends StatelessWidget {
required this.chatController, required this.chatController,
required this.onEdit, required this.onEdit,
required this.onReply, required this.onReply,
this.noAnimated = false,
}); });
bool _checkMessageMergeable(Event? a, Event? b) { bool _checkMessageMergeable(Event? a, Event? b) {
@ -63,15 +66,32 @@ class ChatEventList extends StatelessWidget {
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: ChatEvent( child: Builder(builder: (context) {
key: Key('m${item!.uuid}'), final widget = ChatEvent(
item: item, key: Key('m${item!.uuid}'),
isMerged: isMerged, item: item,
chatController: chatController, isMerged: isMerged,
).paddingOnly( chatController: chatController,
top: !isMerged ? 8 : 0, ).paddingOnly(
bottom: !hasMerged ? 8 : 0, top: !isMerged ? 8 : 0,
), bottom: !hasMerged ? 8 : 0,
);
if (noAnimated) {
return widget;
} else {
return widget
.animate(
key: Key('animated-m${item.uuid}'),
)
.slideY(
curve: Curves.fastLinearToSlowEaseIn,
duration: 250.ms,
begin: 0.5,
end: 0,
);
}
}),
onLongPress: () { onLongPress: () {
showModalBottomSheet( showModalBottomSheet(
useRootNavigator: true, useRootNavigator: true,
@ -79,7 +99,7 @@ class ChatEventList extends StatelessWidget {
builder: (context) => ChatEventAction( builder: (context) => ChatEventAction(
channel: channel, channel: channel,
realm: channel.realm, realm: channel.realm,
item: item, item: item!,
onEdit: () { onEdit: () {
onEdit(item); onEdit(item);
}, },

View File

@ -1,12 +1,16 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown_selectionarea/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:markdown/markdown.dart' as markdown; import 'package:markdown/markdown.dart' as markdown;
import 'package:markdown/markdown.dart'; import 'package:path/path.dart';
import 'package:solian/providers/stickers.dart'; import 'package:solian/providers/stickers.dart';
import 'package:solian/widgets/attachments/attachment_list.dart'; import 'package:solian/widgets/attachments/attachment_list.dart';
import 'package:solian/widgets/auto_cache_image.dart'; import 'package:solian/widgets/auto_cache_image.dart';
import 'package:syntax_highlight/syntax_highlight.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'account/account_profile_popup.dart'; import 'account/account_profile_popup.dart';
@ -39,11 +43,6 @@ class MarkdownTextContent extends StatelessWidget {
// Getting paragraph // Getting paragraph
var paragraph = paragraphs[idx]; var paragraph = paragraphs[idx];
// Auto adding new-lines
if (isAutoWarp) {
paragraph = paragraph.replaceAll('\n', '\\\n');
}
// Matching stickers // Matching stickers
final stickerMatch = stickerRegex.allMatches(paragraph); final stickerMatch = stickerRegex.allMatches(paragraph);
final isOnlySticker = final isOnlySticker =
@ -58,31 +57,48 @@ class MarkdownTextContent extends StatelessWidget {
styleSheet: MarkdownStyleSheet.fromTheme( styleSheet: MarkdownStyleSheet.fromTheme(
Theme.of(context), Theme.of(context),
).copyWith( ).copyWith(
textScaleFactor: isLargeText ? 1.1 : 1, textScaler: TextScaler.linear(isLargeText ? 1.1 : 1),
blockquote: TextStyle( blockquote: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
blockquoteDecoration: BoxDecoration( blockquoteDecoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh, color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: const BorderRadius.all(Radius.circular(4)), borderRadius: const BorderRadius.all(Radius.circular(4)),
), ),
horizontalRuleDecoration: BoxDecoration( horizontalRuleDecoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide( top: BorderSide(
width: 1.0, width: 1.0,
color: Theme.of(context).dividerColor, color: Theme.of(context).dividerColor,
),
), ),
), ),
), codeblockDecoration: BoxDecoration(
), border: Border.all(
color: Theme.of(context).dividerColor,
width: 0.3,
),
borderRadius: const BorderRadius.all(Radius.circular(4)),
color: Theme.of(context).colorScheme.surface.withOpacity(0.5),
)),
builders: {
'code': _MarkdownTextCodeElement(),
},
softLineBreak: true,
extensionSet: markdown.ExtensionSet( extensionSet: markdown.ExtensionSet(
markdown.ExtensionSet.gitHubFlavored.blockSyntaxes, <markdown.BlockSyntax>[
markdown.CodeBlockSyntax(),
...markdown.ExtensionSet.commonMark.blockSyntaxes,
...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes,
],
<markdown.InlineSyntax>[ <markdown.InlineSyntax>[
if (isAutoWarp) markdown.LineBreakSyntax(),
_UserNameCardInlineSyntax(), _UserNameCardInlineSyntax(),
_CustomEmoteInlineSyntax(), _CustomEmoteInlineSyntax(),
markdown.EmojiSyntax(),
markdown.AutolinkSyntax(), markdown.AutolinkSyntax(),
markdown.AutolinkExtensionSyntax(), markdown.AutolinkExtensionSyntax(),
markdown.CodeSyntax(),
...markdown.ExtensionSet.commonMark.inlineSyntaxes,
...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes ...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes
], ],
), ),
@ -184,7 +200,7 @@ class MarkdownTextContent extends StatelessWidget {
); );
if (idx < paragraphs.length - 1) { if (idx < paragraphs.length - 1) {
contentWidgets.add(const Gap(4)); contentWidgets.add(isAutoWarp ? const Gap(4) : const Gap(8));
} }
} }
@ -205,7 +221,7 @@ class MarkdownTextContent extends StatelessWidget {
} }
} }
class _UserNameCardInlineSyntax extends InlineSyntax { class _UserNameCardInlineSyntax extends markdown.InlineSyntax {
_UserNameCardInlineSyntax() : super(r'@[a-zA-Z0-9_]+'); _UserNameCardInlineSyntax() : super(r'@[a-zA-Z0-9_]+');
@override @override
@ -221,7 +237,7 @@ class _UserNameCardInlineSyntax extends InlineSyntax {
} }
} }
class _CustomEmoteInlineSyntax extends InlineSyntax { class _CustomEmoteInlineSyntax extends markdown.InlineSyntax {
_CustomEmoteInlineSyntax() : super(r':([-\w]+):'); _CustomEmoteInlineSyntax() : super(r':([-\w]+):');
@override @override
@ -241,3 +257,48 @@ class _CustomEmoteInlineSyntax extends InlineSyntax {
return true; return true;
} }
} }
class _MarkdownTextCodeElement extends MarkdownElementBuilder {
@override
Widget? visitElementAfter(
markdown.Element element,
TextStyle? preferredStyle,
) {
var language = '';
if (element.attributes['class'] != null) {
String lg = element.attributes['class'] as String;
language = lg.substring(9).trim();
}
return SizedBox(
child: FutureBuilder(
future: (() async {
final docPath = '../../../';
final highlightingPath =
join(docPath, 'assets/highlighting', language);
await Highlighter.initialize([highlightingPath]);
return Highlighter(
language: highlightingPath,
theme: PlatformDispatcher.instance.platformBrightness ==
Brightness.light
? await HighlighterTheme.loadLightTheme()
: await HighlighterTheme.loadDarkTheme(),
);
})(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final highlighter = snapshot.data!;
return Text.rich(
highlighter.highlight(element.textContent.trim()),
style: GoogleFonts.robotoMono(),
);
}
return Text(
element.textContent.trim(),
style: GoogleFonts.robotoMono(),
);
},
),
).paddingAll(8);
}
}

View File

@ -27,39 +27,43 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return NavigationRail( return Material(
selectedIndex: _currentIndex, color: Theme.of(context).colorScheme.surface,
labelType: NavigationRailLabelType.selected, child: NavigationRail(
groupAlignment: -1, selectedIndex: _currentIndex,
destinations: AppNavigation.destinations labelType: NavigationRailLabelType.selected,
.sublist(0, AppNavigation.destinations.length - 1) groupAlignment: -1,
.map( destinations: AppNavigation.destinations
(x) => NavigationRailDestination( .sublist(0, AppNavigation.destinations.length - 1)
icon: x.icon, .map(
label: Text(x.label), (x) => NavigationRailDestination(
icon: x.icon,
label: Text(x.label),
),
)
.toList(),
trailing: Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: IconButton(
icon: AppNavigation.destinations.last.icon,
tooltip: AppNavigation.destinations.last.label,
onPressed: () {
setState(() => _currentIndex = null);
AppRouter.instance
.goNamed(AppNavigation.destinations.last.page);
},
), ),
)
.toList(),
trailing: Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: IconButton(
icon: AppNavigation.destinations.last.icon,
tooltip: AppNavigation.destinations.last.label,
onPressed: () {
setState(() => _currentIndex = null);
AppRouter.instance.goNamed(AppNavigation.destinations.last.page);
},
), ),
), ),
onDestinationSelected: (idx) {
setState(() => _currentIndex = idx);
AppRouter.instance.goNamed(AppNavigation.destinations[idx].page);
},
).paddingOnly(
top: max(16, MediaQuery.of(context).padding.top),
bottom: max(16, MediaQuery.of(context).padding.bottom),
), ),
onDestinationSelected: (idx) {
setState(() => _currentIndex = idx);
AppRouter.instance.goNamed(AppNavigation.destinations[idx].page);
},
).paddingOnly(
top: max(16, MediaQuery.of(context).padding.top),
bottom: max(16, MediaQuery.of(context).padding.bottom),
); );
} }
} }

View File

@ -117,30 +117,16 @@ class _PostItemState extends State<PostItem> {
), ),
), ),
), ),
if (_contentHeight >= 80 && !widget.isFullContent)
Align(
alignment: Alignment.bottomCenter,
child: IgnorePointer(
child: Container(
height: 80,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(context).colorScheme.surfaceContainerLow,
Theme.of(context)
.colorScheme
.surface
.withOpacity(0),
],
),
),
),
),
),
], ],
), ),
if (_contentHeight >= 80 && !widget.isFullContent)
Opacity(
opacity: 0.8,
child: InkWell(child: Text('readMore'.tr)),
).paddingOnly(
left: 12,
top: 4,
),
LinkExpansion(content: item.body['content']).paddingOnly( LinkExpansion(content: item.body['content']).paddingOnly(
left: 8, left: 8,
right: 8, right: 8,
@ -225,34 +211,16 @@ class _PostItemState extends State<PostItem> {
), ),
), ),
), ),
if (_contentHeight >= 320 && !widget.isFullContent)
Align(
alignment: Alignment.bottomCenter,
child: IgnorePointer(
child: Container(
height: 320,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
(widget.backgroundColor ??
Theme.of(context)
.colorScheme
.surface),
(widget.backgroundColor ??
Theme.of(context)
.colorScheme
.surface)
.withOpacity(0),
],
),
),
),
),
),
], ],
), ),
if (_contentHeight >= 320 && !widget.isFullContent)
Opacity(
opacity: 0.8,
child: InkWell(child: Text('readMore'.tr)),
).paddingOnly(
left: 12,
top: 4,
),
if (widget.item.replyTo != null && widget.isShowEmbed) if (widget.item.replyTo != null && widget.isShowEmbed)
Container( Container(
constraints: const BoxConstraints(maxWidth: 480), constraints: const BoxConstraints(maxWidth: 480),
@ -336,8 +304,7 @@ class _PostItemState extends State<PostItem> {
), ),
closedElevation: 0, closedElevation: 0,
openElevation: 0, openElevation: 0,
closedColor: closedColor: Colors.transparent,
widget.backgroundColor ?? Theme.of(context).colorScheme.surface,
openColor: Theme.of(context).colorScheme.surface, openColor: Theme.of(context).colorScheme.surface,
); );
} }
@ -574,7 +541,7 @@ class _PostEmbedWidget extends StatelessWidget {
), ),
closedElevation: 0, closedElevation: 0,
openElevation: 0, openElevation: 0,
closedColor: Theme.of(context).colorScheme.surface, closedColor: Colors.transparent,
openColor: Theme.of(context).colorScheme.surface, openColor: Theme.of(context).colorScheme.surface,
); );
} }

View File

@ -0,0 +1,64 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:solian/platform.dart';
import 'package:solian/theme.dart';
class RootContainer extends StatelessWidget {
final Widget? child;
const RootContainer({super.key, this.child});
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: PlatformInfo.isWeb
? Future.value(null)
: getApplicationDocumentsDirectory(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final path = '${snapshot.data!.path}/app_background_image';
final file = File(path);
if (file.existsSync()) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: Container(
decoration: BoxDecoration(
backgroundBlendMode: BlendMode.darken,
color: Theme.of(context).colorScheme.surface,
image: DecorationImage(
opacity: 0.2,
image: FileImage(file),
fit: BoxFit.cover,
),
),
child: child,
),
);
}
}
return Material(
color: Theme.of(context).colorScheme.surface,
child: child,
);
},
);
}
}
class ResponsiveRootContainer extends StatelessWidget {
final Widget? child;
const ResponsiveRootContainer({super.key, this.child});
@override
Widget build(BuildContext context) {
if (AppTheme.isLargeScreen(context)) {
return child ?? SizedBox.shrink();
} else {
return RootContainer(child: child);
}
}
}

View File

@ -1,12 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:solian/widgets/root_container.dart';
class EmptyPagePlaceholder extends StatelessWidget { class EmptyPagePlaceholder extends StatelessWidget {
const EmptyPagePlaceholder({super.key}); const EmptyPagePlaceholder({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return ResponsiveRootContainer(
color: Theme.of(context).colorScheme.surface,
child: Center( child: Center(
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)), borderRadius: const BorderRadius.all(Radius.circular(12)),

View File

@ -8,38 +8,38 @@ PODS:
- FlutterMacOS - FlutterMacOS
- file_selector_macos (0.0.1): - file_selector_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- Firebase/Analytics (11.0.0): - Firebase/Analytics (11.2.0):
- Firebase/Core - Firebase/Core
- Firebase/Core (11.0.0): - Firebase/Core (11.2.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseAnalytics (~> 11.0.0) - FirebaseAnalytics (~> 11.2.0)
- Firebase/CoreOnly (11.0.0): - Firebase/CoreOnly (11.2.0):
- FirebaseCore (= 11.0.0) - FirebaseCore (= 11.2.0)
- Firebase/Crashlytics (11.0.0): - Firebase/Crashlytics (11.2.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseCrashlytics (~> 11.0.0) - FirebaseCrashlytics (~> 11.2.0)
- Firebase/Messaging (11.0.0): - Firebase/Messaging (11.2.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseMessaging (~> 11.0.0) - FirebaseMessaging (~> 11.2.0)
- firebase_analytics (11.3.2): - firebase_analytics (11.3.3):
- Firebase/Analytics (= 11.0.0) - Firebase/Analytics (= 11.2.0)
- firebase_core - firebase_core
- FlutterMacOS - FlutterMacOS
- firebase_core (3.5.0): - firebase_core (3.6.0):
- Firebase/CoreOnly (~> 11.0.0) - Firebase/CoreOnly (~> 11.2.0)
- FlutterMacOS - FlutterMacOS
- firebase_crashlytics (4.1.2): - firebase_crashlytics (4.1.3):
- Firebase/CoreOnly (~> 11.0.0) - Firebase/CoreOnly (~> 11.2.0)
- Firebase/Crashlytics (~> 11.0.0) - Firebase/Crashlytics (~> 11.2.0)
- firebase_core - firebase_core
- FlutterMacOS - FlutterMacOS
- firebase_messaging (15.1.2): - firebase_messaging (15.1.3):
- Firebase/CoreOnly (~> 11.0.0) - Firebase/CoreOnly (~> 11.2.0)
- Firebase/Messaging (~> 11.0.0) - Firebase/Messaging (~> 11.2.0)
- firebase_core - firebase_core
- FlutterMacOS - FlutterMacOS
- FirebaseAnalytics (11.0.0): - FirebaseAnalytics (11.2.0):
- FirebaseAnalytics/AdIdSupport (= 11.0.0) - FirebaseAnalytics/AdIdSupport (= 11.2.0)
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@ -47,24 +47,24 @@ PODS:
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.0.0): - FirebaseAnalytics/AdIdSupport (11.2.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.0.0) - GoogleAppMeasurement (= 11.2.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseCore (11.0.0): - FirebaseCore (11.2.0):
- FirebaseCoreInternal (~> 11.0) - FirebaseCoreInternal (~> 11.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0) - GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreExtension (11.2.0): - FirebaseCoreExtension (11.3.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseCoreInternal (11.2.0): - FirebaseCoreInternal (11.3.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseCrashlytics (11.0.0): - FirebaseCrashlytics (11.2.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- FirebaseRemoteConfigInterop (~> 11.0) - FirebaseRemoteConfigInterop (~> 11.0)
@ -73,12 +73,12 @@ PODS:
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
- FirebaseInstallations (11.2.0): - FirebaseInstallations (11.3.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
- FirebaseMessaging (11.0.0): - FirebaseMessaging (11.2.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0) - GoogleDataTransport (~> 10.0)
@ -87,8 +87,8 @@ PODS:
- GoogleUtilities/Reachability (~> 8.0) - GoogleUtilities/Reachability (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseRemoteConfigInterop (11.2.0) - FirebaseRemoteConfigInterop (11.3.0)
- FirebaseSessions (11.2.0): - FirebaseSessions (11.3.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseCoreExtension (~> 11.0) - FirebaseCoreExtension (~> 11.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
@ -108,21 +108,21 @@ PODS:
- gal (1.0.0): - gal (1.0.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- GoogleAppMeasurement (11.0.0): - GoogleAppMeasurement (11.2.0):
- GoogleAppMeasurement/AdIdSupport (= 11.0.0) - GoogleAppMeasurement/AdIdSupport (= 11.2.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.0.0): - GoogleAppMeasurement/AdIdSupport (11.2.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.0.0) - GoogleAppMeasurement/WithoutAdIdSupport (= 11.2.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.0.0): - GoogleAppMeasurement/WithoutAdIdSupport (11.2.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
@ -342,26 +342,26 @@ SPEC CHECKSUMS:
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9 Firebase: 98e6bf5278170668a7983e12971a66b2cd57fc8c
firebase_analytics: a2d0d907566e4a48e27745317f05b4b7db85edd9 firebase_analytics: 30ff72f6d4847ff0b479d8edd92fc8582e719072
firebase_core: c55630cdb8a01cf49eae741dd4bc8c93bdd546b8 firebase_core: e88f946a4601cb1854178cb07da241bba5a6508e
firebase_crashlytics: a359f1f75a23e560c8c97b743ab684ba795d7688 firebase_crashlytics: e09adc8a2db53e71b3ef3d085deb688a0c17467a
firebase_messaging: 12726b352752420d073ff075e328cc2f0ca14c47 firebase_messaging: e1b1c1504659e13d66131f62ec22919293cd0d11
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a FirebaseAnalytics: c36efd5710c60c17558650fa58c2066eca7e9265
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383 FirebaseCore: a282032ae9295c795714ded2ec9c522fc237f8da
FirebaseCoreExtension: cda74ddfb001224bd8fd1d6e74698b4ed07803de FirebaseCoreExtension: 30bb063476ef66cd46925243d64ad8b2c8ac3264
FirebaseCoreInternal: 0c569513412da9f3b31bd0b340013bbee8f295c5 FirebaseCoreInternal: ac26d09a70c730e497936430af4e60fb0c68ec4e
FirebaseCrashlytics: 745d8f0221fe49c62865391d1bf56f5a12eeec0b FirebaseCrashlytics: cfc69af5b53565dc6a5e563788809b5778ac4eac
FirebaseInstallations: 771177d89d6c451dc6e50085ec82e2fc77ed0a4a FirebaseInstallations: 58cf94dabf1e2bb2fa87725a9be5c2249171cda0
FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742 FirebaseMessaging: c9ec7b90c399c7a6100297e9d16f8a27fc7f7152
FirebaseRemoteConfigInterop: 477b26fdeb8fb5fbaf22fa9db5343b42289dc7db FirebaseRemoteConfigInterop: c3a5c31b3c22079f41ba1dc645df889d9ce38cb9
FirebaseSessions: adcec8b72d0066a385e3affcd1bcb1ebb3908ce6 FirebaseSessions: 655ff17f3cc1a635cbdc2d69b953878001f9e25b
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
flutter_webrtc: 2b4e4a2de70a1485836e40fd71a7a94c77d49bd9 flutter_webrtc: 2b4e4a2de70a1485836e40fd71a7a94c77d49bd9
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1 gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
GoogleAppMeasurement: 6e49ffac7d3f2c3ded9cc663f912a13b67bbd0de GoogleAppMeasurement: 76d4f8b36b03bd8381fa9a7fe2cc7f99c0a2e93a
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0 in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0

View File

@ -13,10 +13,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: "5fdcea390499dd26c808a3c662df5f4208d6bbc0643072eee94f1476249e2818" sha256: "5534e701a2c505fed1f0799e652dd6ae23bd4d2c4cf797220e5ced5764a7c1c2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.43" version: "1.3.44"
_macros: _macros:
dependency: transitive dependency: transitive
description: dart description: dart
@ -146,10 +146,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.12" version: "2.4.13"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
@ -386,10 +386,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: drift name: drift
sha256: "5b561ec76fff260e1e0593a29ca0d058a140a4b4dfb11dcc0c3813820cd20200" sha256: d6ff1ec6a0f3fa097dda6b776cf601f1f3d88b53b287288e09c1306f394fb1b3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.20.2" version: "2.20.3"
drift_dev: drift_dev:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -466,18 +466,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: file_selector_linux name: file_selector_linux
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.2+1" version: "0.9.3"
file_selector_macos: file_selector_macos:
dependency: transitive dependency: transitive
description: description:
name: file_selector_macos name: file_selector_macos
sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.4+1" version: "0.9.4+2"
file_selector_platform_interface: file_selector_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -490,42 +490,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: file_selector_windows name: file_selector_windows
sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.3+2" version: "0.9.3+3"
firebase_analytics: firebase_analytics:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_analytics name: firebase_analytics
sha256: "9c52c099e9cbb852c7f1d2302c7eb34a15758834eca1877f7a779e6082f9882d" sha256: "2c4e7b548d41b46e8aa08bc3bd1163146be7e6d48f678f2e6dd3114994e42458"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.3.2" version: "11.3.3"
firebase_analytics_platform_interface: firebase_analytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_analytics_platform_interface name: firebase_analytics_platform_interface
sha256: "4ec57aee951832fdbf10ca722bbb83fe0001d6168d6c4cfea9ccee0df6afb1e0" sha256: c259ae890c7d4c5d1675d35936be0b1fcd587fce9645948982cd87ad08df6222
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "4.2.5"
firebase_analytics_web: firebase_analytics_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_analytics_web name: firebase_analytics_web
sha256: "95c594fb1e8960992a607b135459e2f9ea3683dd8d01e6b845cace7c6665ec7e" sha256: "5988d1fd022e55515c2a14811c9b5104c32acde115874a9a69ff7c77c4c05cd9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.10+1" version: "0.5.10+2"
firebase_core: firebase_core:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
sha256: c7de9354eb2cd8bfe8059e1112174c9a58beda7051807207306bc48283277cfb sha256: "51dfe2fbf3a984787a2e7b8592f2f05c986bfedd6fdacea3f9e0a7beb334de96"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.0" version: "3.6.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -546,66 +546,66 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_crashlytics name: firebase_crashlytics
sha256: "7821f9d8373b91f2a5ca8214226891d5870e196a7376f66350f65204387e9c15" sha256: "6899800fff1af819955aef740f18c4c8600f8b952a2a1ea97bc0872ebb257387"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.3"
firebase_crashlytics_platform_interface: firebase_crashlytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics_platform_interface name: firebase_crashlytics_platform_interface
sha256: "8ed539fd9e9b6c07905f9f44c5f6d4785ac841a5a8195bd35586c8b1d54ec26d" sha256: "97c47b0a1779a3d4118416a3f0c6c564cc59ad89095e899893204d4b2ad08f4c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.6.43" version: "3.6.44"
firebase_messaging: firebase_messaging:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_messaging name: firebase_messaging
sha256: "32ce60b747e755b48d7112d728d4f736ba82acd98ec825626558d444d385fa3a" sha256: eb6e28a3a35deda61fe8634967c84215efc19133ba58d8e0fc6c9a2af2cba05e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.1.2" version: "15.1.3"
firebase_messaging_platform_interface: firebase_messaging_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_platform_interface name: firebase_messaging_platform_interface
sha256: "69671a0f1a40c7b7c46ad0283e6f34ca2a59a0362ca14a240a4ea01c46e8a521" sha256: b316c4ee10d93d32c033644207afc282d9b2b4372f3cf9c6022f3558b3873d2d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.45" version: "4.5.46"
firebase_messaging_web: firebase_messaging_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_web name: firebase_messaging_web
sha256: "6890111a9d01d7b13d0f6fe74850812c334e903d2c80a2d9356a3abb8c3a9e9a" sha256: d7f0147a1a9fe4313168e20154a01fd5cf332898de1527d3930ff77b8c7f5387
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.9.1" version: "3.9.2"
firebase_performance: firebase_performance:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_performance name: firebase_performance
sha256: ed9a408b6d1f221fc0e2890dcf0733b604d1aea6cd3b897f97dd3f889f01ddfc sha256: "0df8208afad64aa1d774bd267033312284bd73e68210caf6936dc1f8a8fa0878"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.10.0+7" version: "0.10.0+8"
firebase_performance_platform_interface: firebase_performance_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_performance_platform_interface name: firebase_performance_platform_interface
sha256: bfcfbfcefeaf3853a72602675b786e13a609d49ac70fc325d302b5794b8b0c06 sha256: "97cc3fcda0a835142ff2e93b19dd72904d666a576e196f12cdaf492921e6ea44"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.4+43" version: "0.1.4+44"
firebase_performance_web: firebase_performance_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_performance_web name: firebase_performance_web
sha256: "2ac9e44a1be7b20f1a7a3912d84bf2e1ec76398f2dadc07b6b7c3173d590e329" sha256: "322a4ae99cb952cdfd788399f52421dd6ecd713723ac7e5e6bb7856bb28ef270"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.7+1" version: "0.1.7+2"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -751,18 +751,18 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_launcher_icons name: flutter_launcher_icons
sha256: a38f2f1b3c373d42bf08bd17d60e20d3c73abce7727607b4d085ec7d5acaa294 sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.14.0" version: "0.14.1"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "5.0.0"
flutter_local_notifications: flutter_local_notifications:
dependency: "direct main" dependency: "direct main"
description: description:
@ -791,18 +791,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: a23c41ee57573e62fc2190a1f36a0480c4d90bde3a8a8d7126e5d5992fb53fb7 sha256: e17575ca576a34b46c58c91f9948891117a1bd97815d2e661813c7f90c647a78
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.3+1" version: "0.7.3+2"
flutter_markdown_selectionarea:
dependency: "direct main"
description:
name: flutter_markdown_selectionarea
sha256: d4bc27e70a5c40ebdab23a4b81f75d53696a214d4d1f13c12045b38a0ddc58a2
url: "https://pub.dev"
source: hosted
version: "0.6.17+1"
flutter_native_splash: flutter_native_splash:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -815,10 +807,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.22" version: "2.0.23"
flutter_secure_storage: flutter_secure_storage:
dependency: "direct main" dependency: "direct main"
description: description:
@ -871,10 +863,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_shaders name: flutter_shaders
sha256: "02750b545c01ff4d8e9bbe8f27a7731aa3778402506c67daa1de7f5fc3f4befe" sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.2" version: "0.1.3"
flutter_staggered_grid_view: flutter_staggered_grid_view:
dependency: transitive dependency: transitive
description: description:
@ -969,10 +961,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: go_router name: go_router
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459" sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.7" version: "14.3.0"
google_fonts: google_fonts:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1073,10 +1065,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image_picker_android name: image_picker_android
sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85 sha256: d3e5e00fdfeca8fd4ffb3227001264d449cc8950414c2ff70b0e06b9c628e643
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.12+13" version: "0.8.12+15"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
@ -1217,10 +1209,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "5.0.0"
livekit_client: livekit_client:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1457,10 +1449,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" sha256: f7544c346a0742aee1450f9e5c0f5269d7c602b9c95fdbcd9fb8f5b1df13b1cc
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.10" version: "2.2.11"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -1801,18 +1793,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.3"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.5.3"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@ -1910,18 +1902,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite name: sqflite
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d sha256: ff5a2436ef8ebdfda748fbfe957f9981524cb5ff11e7bafa8c42771840e8a788
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.3+1" version: "2.3.3+2"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "4058172e418eb7e7f2058dcb7657d451a8fc264afa0dea4dbd0f304a57131611" sha256: "2d8e607db72e9cb7748c9c6e739e2c9618320a5517de693d5a24609c4671b1a4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4+3" version: "2.5.4+4"
sqlite3: sqlite3:
dependency: transitive dependency: transitive
description: description:
@ -1982,10 +1974,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: synchronized name: synchronized
sha256: "51b08572b9f091f8c3eb4d9d4be253f196ff0075d5ec9b10a884026d5b55d7bc" sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.0+2" version: "3.3.0+3"
syntax_highlight:
dependency: "direct main"
description:
name: syntax_highlight
sha256: ee33b6aa82cc722bb9b40152a792181dee222353b486c0255fde666a3e3a4997
url: "https://pub.dev"
source: hosted
version: "0.4.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -2070,10 +2070,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab sha256: "8fc3bae0b68c02c47c5c86fa8bfa74471d42687b0eded01b78de87872db745e2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.10" version: "6.3.12"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@ -2126,10 +2126,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: uuid name: uuid
sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77 sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.0" version: "4.5.1"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
@ -2222,10 +2222,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.1.0"
web_socket: web_socket:
dependency: transitive dependency: transitive
description: description:
@ -2254,10 +2254,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" sha256: "4d45dc9069dba4619dc0ebd93c7cec5e66d8482cb625a370ac806dcc8165f2ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.5.4" version: "5.5.5"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:
@ -2270,10 +2270,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.1.0"
xml: xml:
dependency: transitive dependency: transitive
description: description:

View File

@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App" description: "The Solar Network App"
publish_to: "none" publish_to: "none"
version: 1.3.6+2 version: 1.3.6+5
environment: environment:
sdk: ">=3.3.4 <4.0.0" sdk: ">=3.3.4 <4.0.0"
@ -49,7 +49,6 @@ dependencies:
dismissible_page: ^1.0.2 dismissible_page: ^1.0.2
share_plus: ^10.0.0 share_plus: ^10.0.0
flutter_cache_manager: ^3.3.3 flutter_cache_manager: ^3.3.3
flutter_markdown_selectionarea: ^0.6.17+1
shared_preferences: ^2.2.3 shared_preferences: ^2.2.3
provider: ^6.1.2 provider: ^6.1.2
gal: ^2.3.0 gal: ^2.3.0
@ -84,12 +83,13 @@ dependencies:
version: ^3.0.2 version: ^3.0.2
action_slider: ^0.7.0 action_slider: ^0.7.0
in_app_review: ^2.0.9 in_app_review: ^2.0.9
syntax_highlight: ^0.4.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^4.0.0 flutter_lints: ^5.0.0
flutter_launcher_icons: ^0.14.0 flutter_launcher_icons: ^0.14.0
build_runner: ^2.4.12 build_runner: ^2.4.12
@ -103,6 +103,7 @@ flutter:
assets: assets:
- assets/logo.png - assets/logo.png
- assets/locales/ - assets/locales/
- assets/highlighting/
fonts: fonts:
- family: NotoSansEmoji - family: NotoSansEmoji