Repo created
This commit is contained in:
parent
75dc487a7a
commit
39c29d175b
6317 changed files with 388324 additions and 2 deletions
25
.editorconfig
Normal file
25
.editorconfig
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
ij_continuation_indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
end_of_line = lf
|
||||||
|
max_line_length = 120
|
||||||
|
|
||||||
|
[*.{kt,kts}]
|
||||||
|
ij_kotlin_imports_layout = *,^
|
||||||
|
ij_kotlin_allow_trailing_comma = true
|
||||||
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
|
|
||||||
|
[*.{yml,yaml,json,toml}]
|
||||||
|
indent_size = 2
|
||||||
|
tab_width = 2
|
||||||
|
ij_continuation_indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
*.bat eol=crlf
|
||||||
|
*.eml eol=crlf
|
||||||
|
*.jar binary
|
||||||
|
|
||||||
|
app-k9mail/build.gradle.kts merge=merge_gradle
|
||||||
|
app-thunderbird/build.gradle.kts merge=merge_gradle
|
||||||
|
app-k9mail/src/main/res/raw/changelog_master.xml merge=ours
|
||||||
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Kotlin
|
||||||
|
.kotlin/
|
||||||
|
|
||||||
|
# mdBook
|
||||||
|
book/
|
||||||
|
|
||||||
|
# Generated folders
|
||||||
|
bin/
|
||||||
|
build/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
*.class
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
|
||||||
|
# Signing files
|
||||||
|
.signing/
|
||||||
|
*.signing.properties
|
||||||
|
|
||||||
|
# IDEA/Android Studio ignores
|
||||||
|
*iml
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
# IDEA/Android Studio includes
|
||||||
|
!.idea/icon.png
|
||||||
|
!.idea/codeStyles/
|
||||||
|
!.idea/fileTemplates/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# Mac thumbnail db
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Screenshots
|
||||||
|
adb-screenshots/
|
||||||
447
.idea/codeStyles/Project.xml
generated
Normal file
447
.idea/codeStyles/Project.xml
generated
Normal file
|
|
@ -0,0 +1,447 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="LINE_SEPARATOR" value=" " />
|
||||||
|
<option name="RIGHT_MARGIN" value="120" />
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
<AndroidXmlCodeStyleSettings>
|
||||||
|
<option name="LAYOUT_SETTINGS">
|
||||||
|
<value>
|
||||||
|
<option name="INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION" value="true" />
|
||||||
|
<option name="INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE" value="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="MANIFEST_SETTINGS">
|
||||||
|
<value>
|
||||||
|
<option name="INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION" value="true" />
|
||||||
|
<option name="INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE" value="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="OTHER_SETTINGS">
|
||||||
|
<value>
|
||||||
|
<option name="INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION" value="true" />
|
||||||
|
<option name="INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE" value="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</AndroidXmlCodeStyleSettings>
|
||||||
|
<JavaCodeStyleSettings>
|
||||||
|
<option name="SPACE_AROUND_TYPE_BOUNDS_IN_TYPE_PARAMETERS" value="false" />
|
||||||
|
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||||
|
<option name="IMPORT_LAYOUT_TABLE">
|
||||||
|
<value>
|
||||||
|
<package name="" withSubpackages="true" static="false" module="true" />
|
||||||
|
<package name="java" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="android" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="" withSubpackages="true" static="true" />
|
||||||
|
<emptyLine />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</JavaCodeStyleSettings>
|
||||||
|
<JetCodeStyleSettings>
|
||||||
|
<option name="PACKAGES_IMPORT_LAYOUT">
|
||||||
|
<value>
|
||||||
|
<package name="" alias="false" withSubpackages="true" />
|
||||||
|
<package name="" alias="true" withSubpackages="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="99" />
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="99" />
|
||||||
|
<option name="ALLOW_TRAILING_COMMA" value="true" />
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</JetCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
|
||||||
|
<option name="BLANK_LINES_AFTER_PACKAGE" value="2" />
|
||||||
|
<option name="BLANK_LINES_AFTER_IMPORTS" value="2" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||||
|
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
|
||||||
|
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||||
|
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||||
|
<option name="RESOURCE_LIST_WRAP" value="1" />
|
||||||
|
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||||
|
<option name="THROWS_LIST_WRAP" value="1" />
|
||||||
|
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
|
||||||
|
<option name="THROWS_KEYWORD_WRAP" value="1" />
|
||||||
|
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||||
|
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||||
|
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||||
|
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||||
|
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||||
|
<option name="ASSIGNMENT_WRAP" value="1" />
|
||||||
|
<option name="WRAP_COMMENTS" value="true" />
|
||||||
|
<option name="ASSERT_STATEMENT_WRAP" value="1" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<FINAL />
|
||||||
|
<PUBLIC />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<FINAL />
|
||||||
|
<PROTECTED />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<FINAL />
|
||||||
|
<PACKAGE_PRIVATE />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<FINAL />
|
||||||
|
<PRIVATE />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<PUBLIC />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<PROTECTED />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<PACKAGE_PRIVATE />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<PRIVATE />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<FINAL />
|
||||||
|
<PUBLIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<FINAL />
|
||||||
|
<PROTECTED />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<FINAL />
|
||||||
|
<PACKAGE_PRIVATE />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<FINAL />
|
||||||
|
<PRIVATE />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<PUBLIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<PROTECTED />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<PACKAGE_PRIVATE />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<FIELD />
|
||||||
|
<PRIVATE />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<FIELD />
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<METHOD />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<CONSTRUCTOR />
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<METHOD />
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<ENUM />
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<INTERFACE />
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<CLASS />
|
||||||
|
<STATIC />
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<CLASS />
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="kotlin">
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
BIN
.idea/icon.png
generated
Normal file
BIN
.idea/icon.png
generated
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
1
.java-version
Normal file
1
.java-version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
21
|
||||||
3
CODE_OF_CONDUCT.md
Normal file
3
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
This repository is governed by Mozilla's code of conduct and etiquette guidelines. For more details please see the [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
|
||||||
202
LICENSE
Normal file
202
LICENSE
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
3
NOTICE
Normal file
3
NOTICE
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
K-9 Mail
|
||||||
|
Copyright 2008-2016, K-9 Mail Developers
|
||||||
|
Copyright 2005-2016, The Android Open Source Project
|
||||||
90
README.md
90
README.md
|
|
@ -1,3 +1,89 @@
|
||||||
# k9-mail
|
# Thunderbird for Android
|
||||||
|
|
||||||
Android Mail Client
|
<a href="https://play.google.com/store/apps/details?id=net.thunderbird.android&referrer=utm_campaign%3Dandroid_metadata%26utm_medium%3Dweb%26utm_source%3Dgithub.com%26utm_content%3Dbadge" target="_blank"><img src="./docs/assets/get-it-on-play.png" alt="Get it on Google Play" height="28"></a>
|
||||||
|
<a href="https://f-droid.org/packages/net.thunderbird.android"><img src="./docs/assets/get-it-on-fdroid.png" alt="Get it on F-Droid" height="28"></a>
|
||||||
|
[](https://github.com/thunderbird/thunderbird-android/releases/latest)
|
||||||
|
[](https://github.com/thunderbird/thunderbird-android/releases)
|
||||||
|
|
||||||
|
Thunderbird for Android is a powerful, privacy-focused email app. Effortlessly manage multiple email accounts from one app, with a Unified Inbox option for maximum productivity. Built on open-source technology and supported by a dedicated team of developers alongside a global community of volunteers, Thunderbird never treats your private data as a product.
|
||||||
|
|
||||||
|
Thunderbird for Android is based on K-9 Mail, which comes with a rich history of success and functionality in open source email.
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
Thunderbird for Android can be downloaded from a couple of sources:
|
||||||
|
|
||||||
|
- Thunderbird on [Google Play](https://play.google.com/store/apps/details?id=net.thunderbird.android&referrer=utm_campaign%3Dandroid_metadata%26utm_medium%3Dweb%26utm_source%3Dgithub.com%26utm_content%3Dlink) or [F-Droid](https://f-droid.org/packages/net.thunderbird.android)
|
||||||
|
- Thunderbird Beta on [Google Play](https://play.google.com/store/apps/details?id=net.thunderbird.android.beta&referrer=utm_campaign%3Dandroid_metadata%26utm_medium%3Dweb%26utm_source%3Dgithub.com%26utm_content%3Dlink) or [F-Droid](https://f-droid.org/packages/net.thunderbird.android.beta)
|
||||||
|
- [Github Releases](https://github.com/thunderbird/thunderbird-android/releases)
|
||||||
|
- [FFUpdater](https://f-droid.org/packages/de.marmaro.krt.ffupdater/) allows installing the latest versions from ftp.mozilla.org
|
||||||
|
|
||||||
|
By using Thunderbird for Android Beta, you have early access to current development and are able to try new features earlier.
|
||||||
|
|
||||||
|
Check out the [Release Notes](https://github.com/thunderbird/thunderbird-android/releases) to find out what changed in each version of Thunderbird for Android.
|
||||||
|
|
||||||
|
The SHA-256 fingerprints for our signing certificates are available in [SECURITY.md](./SECURITY.md#verifying-fingerprints).
|
||||||
|
|
||||||
|
## Need Help? Found a bug? Have an idea? Want to chat?
|
||||||
|
|
||||||
|
If the app is not behaving like it should, or you are not sure if you've encountered a bug:
|
||||||
|
|
||||||
|
- Check out our [knowledge base](https://support.mozilla.org/products/thunderbird-android) and [frequently asked questions](https://support.mozilla.org/kb/thunderbird-android-8-faq)
|
||||||
|
- Ask a question on our [support forum](https://support.mozilla.org/en-US/questions/new/thunderbird-android)
|
||||||
|
|
||||||
|
If you are certain you've identified a bug in Thunderbird for Android and would like to help fix it:
|
||||||
|
|
||||||
|
- File an issue on [our GitHub issue tracker](https://github.com/thunderbird/thunderbird-android/issues)
|
||||||
|
|
||||||
|
If you have an idea how to improve Thunderbird for Android:
|
||||||
|
|
||||||
|
- Tell us about and vote on your feature ideas on [connect.mozilla.org](https://connect.mozilla.org/t5/ideas/idb-p/ideas/label-name/thunderbird%20android).
|
||||||
|
- Join the discussion about the latest changes in the [Thunderbird Android Beta Topicbox](https://thunderbird.topicbox.com/groups/android-beta).
|
||||||
|
|
||||||
|
The Thunderbird Community uses Matrix to communicate:
|
||||||
|
|
||||||
|
- General chat about Thunderbird for Android and K-9 Mail: [#tb-android:mozilla.org](https://matrix.to/#/#tb-android:mozilla.org)
|
||||||
|
- Development and other ways to contribute: [#tb-android-dev:mozilla.org](https://matrix.to/#/#tb-android-dev:mozilla.org)
|
||||||
|
- Reach the broader Thunderbird Community in the [community space](https://matrix.to/#/#thunderbird-community:mozilla.org)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome contributions from everyone.
|
||||||
|
|
||||||
|
- Development: Have you done a little bit of Kotlin? The [CONTRIBUTING](docs/CONTRIBUTING.md) guide will help you get started
|
||||||
|
- Translations: Do you speak a language aside from English? [Translating is easy](https://hosted.weblate.org/projects/tb-android/) and just takes a few minutes for your first success.
|
||||||
|
- We have [a number of other contribution opportunities](https://blog.thunderbird.net/2024/09/contribute-to-thunderbird-for-android/) available.
|
||||||
|
- Thunderbird is supported solely by financial contributions from users like you. [Make a financial contribution today](https://www.thunderbird.net/donate/mobile/?form=tfa)!
|
||||||
|
- Make sure to check out the [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
|
||||||
|
|
||||||
|
### Architecture Decision Records (ADR)
|
||||||
|
|
||||||
|
We use [Architecture Decision Records](https://adr.github.io/) to document the architectural decisions made in the
|
||||||
|
development of Thunderbird for Android. You can find them in the [`docs/architecture/adr`](docs/architecture/adr) directory.
|
||||||
|
|
||||||
|
For more information about our ADRs, please see the [ADRs README](docs/architecture/adr/README.md).
|
||||||
|
|
||||||
|
We encourage team members and contributors to read through our ADRs to understand the architectural decisions that
|
||||||
|
have shaped this project so far. Feel free to propose new ADRs or suggest modifications to existing ones as needed.
|
||||||
|
|
||||||
|
## K-9 Mail
|
||||||
|
|
||||||
|
In June 2022, [K-9 Mail joined the Thunderbird family](https://k9mail.app/2022/06/13/K-9-Mail-and-Thunderbird.html)
|
||||||
|
as the foundation for Thunderbird on Android. Since then, we’ve been updating both apps to give
|
||||||
|
users the same solid experience, so it’s normal to notice that K-9 Mail and Thunderbird look and
|
||||||
|
feel nearly identical. They’re built on the same code, and that’s intentional. You'll notice some
|
||||||
|
features are selectively enabled for Thunderbird as opposed to K-9 Mail, usually when they are
|
||||||
|
simply a better fit for Thunderbird (like the import from K-9 functionality).
|
||||||
|
|
||||||
|
If you prefer the robot dog and would like to keep K-9 Mail around, you can find it here:
|
||||||
|
|
||||||
|
- [K-9 Mail on Google Play](https://play.google.com/store/apps/details?id=com.fsck.k9&utm_source=thunderbird-android-github&utm_campaign=download-section)
|
||||||
|
- [K-9 Mail on F-Droid](https://f-droid.org/packages/com.fsck.k9/)
|
||||||
|
|
||||||
|
## Forking
|
||||||
|
|
||||||
|
If you want to use a fork of this project please ensure that you replace the OAuth client setup in the `app-k9mail/src/{debug,release}/kotlin/app/k9mail/auth/K9OAuthConfigurationFactory.kt` and `app-thunderbird/src/{debug,daily,beta,release}/kotlin/net/thunderbird/android/auth/TbOAuthConfigurationFactory.kt` with your own OAuth client setup and ensure that the `redirectUri` is different to the one used in the main project. This is to prevent conflicts with the main app when both are installed on the same device.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Thunderbird for Android is licensed under the [Apache License, Version 2.0](LICENSE).
|
||||||
|
|
|
||||||
29
SECURITY.md
Normal file
29
SECURITY.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Thunderbird for Android Security
|
||||||
|
|
||||||
|
## Security Audit
|
||||||
|
|
||||||
|
The code in this repository underwent an extensive security audit in collaboration with the Open Source Technology
|
||||||
|
Improvement Fund ([OSTIF](https://ostif.org/)) and [7ASecurity](https://7asecurity.com/) in the first half of 2023. For
|
||||||
|
more details, see
|
||||||
|
our [blog post](https://blog.thunderbird.net/2023/07/k-9-mail-collaborates-with-ostif-and-7asecurity-security-audit/).
|
||||||
|
|
||||||
|
## Verifying Fingerprints
|
||||||
|
|
||||||
|
These are the SHA-256 fingerprints for our signing certificates:
|
||||||
|
|
||||||
|
- Thunderbird: `B6:52:47:79:B3:DB:BC:5A:C1:7A:5A:C2:71:DD:B2:9D:CF:BF:72:35:78:C2:38:E0:3C:3C:21:78:11:35:6D:D1`
|
||||||
|
- Thunderbird Beta: `05:6B:FA:FB:45:02:49:50:2F:D9:22:62:28:70:4C:25:29:E1:B8:22:DA:06:76:0D:47:A8:5C:95:57:74:1F:BD`
|
||||||
|
- K-9 Mail: `55:C8:A5:23:B9:73:35:F5:BF:60:DF:E8:A9:F3:E1:DD:E7:44:51:6D:93:57:E8:0A:92:5B:7B:22:E4:F5:55:24`
|
||||||
|
|
||||||
|
You can use the following command to retrieve and [verify](https://developer.android.com/tools/apksigner#usage-verify)
|
||||||
|
the certificate before installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apksigner verify -v --print-certs <path-to-apk>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reporting Vulnerabilities
|
||||||
|
|
||||||
|
You can report a security vulnerability through the [vulnerability reporting form](https://github.com/thunderbird/thunderbird-android/security/advisories/new).
|
||||||
|
|
||||||
|
We appreciate your support in making Thunderbird for Android as safe as possible!
|
||||||
7
app-common/README.md
Normal file
7
app-common/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# App Common
|
||||||
|
|
||||||
|
# App Common
|
||||||
|
|
||||||
|
This is the central integration point for shared code among the K-9 Mail and Thunderbird for Android applications. Its purpose is to collect and organize the individual feature modules that contain the actual functionality, as well as the "glue code" and configurations that tie them together.
|
||||||
|
|
||||||
|
By keeping the shared code focused on these boundaries, we can ensure that it remains lean and avoids unnecessary dependencies. This approach allows us to maintain a clean and modular architecture, making it easier to maintain and update the codebase.
|
||||||
48
app-common/build.gradle.kts
Normal file
48
app-common/build.gradle.kts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
plugins {
|
||||||
|
id(ThunderbirdPlugins.Library.android)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "net.thunderbird.app.common"
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(projects.legacy.common)
|
||||||
|
api(projects.legacy.ui.legacy)
|
||||||
|
|
||||||
|
api(projects.feature.account.core)
|
||||||
|
api(projects.feature.launcher)
|
||||||
|
api(projects.feature.navigation.drawer.api)
|
||||||
|
|
||||||
|
implementation(projects.legacy.core)
|
||||||
|
implementation(projects.core.android.account)
|
||||||
|
|
||||||
|
implementation(projects.core.logging.api)
|
||||||
|
implementation(projects.core.logging.implComposite)
|
||||||
|
implementation(projects.core.logging.implConsole)
|
||||||
|
implementation(projects.core.logging.implLegacy)
|
||||||
|
implementation(projects.core.logging.implFile)
|
||||||
|
|
||||||
|
implementation(projects.core.featureflag)
|
||||||
|
implementation(projects.core.ui.legacy.theme2.common)
|
||||||
|
|
||||||
|
implementation(projects.feature.account.avatar.api)
|
||||||
|
implementation(projects.feature.account.avatar.impl)
|
||||||
|
implementation(projects.feature.account.setup)
|
||||||
|
implementation(projects.feature.mail.account.api)
|
||||||
|
implementation(projects.feature.migration.provider)
|
||||||
|
implementation(projects.feature.notification.api)
|
||||||
|
implementation(projects.feature.notification.impl)
|
||||||
|
implementation(projects.feature.widget.messageList)
|
||||||
|
|
||||||
|
implementation(projects.mail.protocols.imap)
|
||||||
|
|
||||||
|
implementation(libs.androidx.work.runtime)
|
||||||
|
implementation(libs.androidx.lifecycle.process)
|
||||||
|
|
||||||
|
testImplementation(projects.feature.account.fake)
|
||||||
|
}
|
||||||
22
app-common/src/main/AndroidManifest.xml
Normal file
22
app-common/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:installLocation="auto"
|
||||||
|
>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:hasFragileUserData="false"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<!-- Allow access to external text processing actions so they can be displayed in the text selection toolbar -->
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package net.thunderbird.app.common
|
||||||
|
|
||||||
|
import com.fsck.k9.legacyCommonAppModules
|
||||||
|
import com.fsck.k9.legacyCoreModules
|
||||||
|
import com.fsck.k9.legacyUiModules
|
||||||
|
import net.thunderbird.app.common.account.appCommonAccountModule
|
||||||
|
import net.thunderbird.app.common.core.appCommonCoreModule
|
||||||
|
import net.thunderbird.app.common.feature.appCommonFeatureModule
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val appCommonModule: Module = module {
|
||||||
|
includes(legacyCommonAppModules)
|
||||||
|
includes(legacyCoreModules)
|
||||||
|
includes(legacyUiModules)
|
||||||
|
|
||||||
|
includes(
|
||||||
|
appCommonAccountModule,
|
||||||
|
appCommonCoreModule,
|
||||||
|
appCommonFeatureModule,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
package net.thunderbird.app.common
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import app.k9mail.feature.widget.message.list.MessageListWidgetManager
|
||||||
|
import app.k9mail.legacy.di.DI
|
||||||
|
import com.fsck.k9.Core
|
||||||
|
import com.fsck.k9.K9
|
||||||
|
import com.fsck.k9.MessagingListenerProvider
|
||||||
|
import com.fsck.k9.controller.MessagingController
|
||||||
|
import com.fsck.k9.job.WorkManagerConfigurationProvider
|
||||||
|
import com.fsck.k9.notification.NotificationChannelManager
|
||||||
|
import com.fsck.k9.ui.base.AppLanguageManager
|
||||||
|
import com.fsck.k9.ui.base.extensions.currentLocale
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.drop
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import net.thunderbird.app.common.feature.LoggerLifecycleObserver
|
||||||
|
import net.thunderbird.core.common.exception.ExceptionHandler
|
||||||
|
import net.thunderbird.core.logging.Logger
|
||||||
|
import net.thunderbird.core.logging.file.FileLogSink
|
||||||
|
import net.thunderbird.core.logging.legacy.Log
|
||||||
|
import net.thunderbird.core.ui.theme.manager.ThemeManager
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import androidx.work.Configuration as WorkManagerConfiguration
|
||||||
|
|
||||||
|
abstract class BaseApplication : Application(), WorkManagerConfiguration.Provider {
|
||||||
|
|
||||||
|
private val messagingController: MessagingController by inject()
|
||||||
|
private val messagingListenerProvider: MessagingListenerProvider by inject()
|
||||||
|
private val themeManager: ThemeManager by inject()
|
||||||
|
private val appLanguageManager: AppLanguageManager by inject()
|
||||||
|
private val notificationChannelManager: NotificationChannelManager by inject()
|
||||||
|
private val messageListWidgetManager: MessageListWidgetManager by inject()
|
||||||
|
private val workManagerConfigurationProvider: WorkManagerConfigurationProvider by inject()
|
||||||
|
private val logger: Logger by inject()
|
||||||
|
private val syncDebugFileLogSink: FileLogSink by inject(named("syncDebug"))
|
||||||
|
|
||||||
|
private val appCoroutineScope: CoroutineScope = MainScope()
|
||||||
|
private var appLanguageManagerInitialized = false
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context?) {
|
||||||
|
Core.earlyInit()
|
||||||
|
|
||||||
|
// Start Koin early so it is ready by the time content providers are initialized.
|
||||||
|
DI.start(this, listOf(provideAppModule()))
|
||||||
|
Log.logger = logger
|
||||||
|
|
||||||
|
super.attachBaseContext(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
K9.init(this)
|
||||||
|
Core.init(this)
|
||||||
|
initializeAppLanguage()
|
||||||
|
updateNotificationChannelsOnAppLanguageChanges()
|
||||||
|
themeManager.init()
|
||||||
|
messageListWidgetManager.init()
|
||||||
|
|
||||||
|
messagingListenerProvider.listeners.forEach { listener ->
|
||||||
|
messagingController.addListener(listener)
|
||||||
|
}
|
||||||
|
val originalHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(ExceptionHandler(originalHandler))
|
||||||
|
|
||||||
|
ProcessLifecycleOwner.get().lifecycle.addObserver(LoggerLifecycleObserver(syncDebugFileLogSink))
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun provideAppModule(): Module
|
||||||
|
|
||||||
|
private fun initializeAppLanguage() {
|
||||||
|
appLanguageManager.init()
|
||||||
|
applyOverrideLocaleToConfiguration()
|
||||||
|
appLanguageManagerInitialized = true
|
||||||
|
listenForAppLanguageChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyOverrideLocaleToConfiguration() {
|
||||||
|
appLanguageManager.getOverrideLocale()?.let { overrideLocale ->
|
||||||
|
updateConfigurationWithLocale(superResources.configuration, overrideLocale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun listenForAppLanguageChanges() {
|
||||||
|
appLanguageManager.overrideLocale
|
||||||
|
.drop(1) // We already applied the initial value
|
||||||
|
.onEach { overrideLocale ->
|
||||||
|
val locale = overrideLocale ?: Locale.getDefault()
|
||||||
|
updateConfigurationWithLocale(superResources.configuration, locale)
|
||||||
|
}
|
||||||
|
.launchIn(appCoroutineScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfiguration: Configuration) {
|
||||||
|
applyOverrideLocaleToConfiguration()
|
||||||
|
super.onConfigurationChanged(superResources.configuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateConfigurationWithLocale(configuration: Configuration, locale: Locale) {
|
||||||
|
Log.d("Updating application configuration with locale '$locale'")
|
||||||
|
|
||||||
|
val newConfiguration = Configuration(configuration).apply {
|
||||||
|
currentLocale = locale
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
superResources.updateConfiguration(newConfiguration, superResources.displayMetrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val superResources: Resources
|
||||||
|
get() = super.getResources()
|
||||||
|
|
||||||
|
// Creating a WebView instance triggers something that will cause the configuration of the Application's Resources
|
||||||
|
// instance to be reset to the default, i.e. not containing our locale override. Unfortunately, we're not notified
|
||||||
|
// about this event. So we're checking each time someone asks for the Resources instance whether we need to change
|
||||||
|
// the configuration again. Luckily, right now (Android 11), the platform is calling this method right after
|
||||||
|
// resetting the configuration.
|
||||||
|
override fun getResources(): Resources {
|
||||||
|
val resources = super.getResources()
|
||||||
|
|
||||||
|
if (appLanguageManagerInitialized) {
|
||||||
|
appLanguageManager.getOverrideLocale()?.let { overrideLocale ->
|
||||||
|
if (resources.configuration.currentLocale != overrideLocale) {
|
||||||
|
Log.w("Resources configuration was reset. Re-applying locale override.")
|
||||||
|
appLanguageManager.applyOverrideLocale()
|
||||||
|
applyOverrideLocaleToConfiguration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNotificationChannelsOnAppLanguageChanges() {
|
||||||
|
appLanguageManager.appLocale
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.onEach { notificationChannelManager.updateChannels() }
|
||||||
|
.launchIn(appCoroutineScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val workManagerConfiguration: WorkManagerConfiguration
|
||||||
|
get() = workManagerConfigurationProvider.getConfiguration()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package net.thunderbird.app.common.account
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import app.k9mail.core.ui.legacy.theme2.common.R
|
||||||
|
import net.thunderbird.core.android.account.AccountManager
|
||||||
|
|
||||||
|
internal class AccountColorPicker(
|
||||||
|
private val accountManager: AccountManager,
|
||||||
|
private val resources: Resources,
|
||||||
|
) {
|
||||||
|
fun pickColor(): Int {
|
||||||
|
val accounts = accountManager.getAccounts()
|
||||||
|
val usedAccountColors = accounts.map { it.chipColor }.toSet()
|
||||||
|
val accountColors = resources.getIntArray(R.array.account_colors).toList()
|
||||||
|
|
||||||
|
val availableColors = accountColors - usedAccountColors
|
||||||
|
if (availableColors.isEmpty()) {
|
||||||
|
return accountColors.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
val defaultAccountColors = resources.getIntArray(R.array.default_account_colors)
|
||||||
|
return availableColors.shuffled().minByOrNull { color ->
|
||||||
|
val index = defaultAccountColors.indexOf(color)
|
||||||
|
if (index != -1) index else defaultAccountColors.size
|
||||||
|
} ?: error("availableColors must not be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
package net.thunderbird.app.common.account
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import app.k9mail.feature.account.common.domain.entity.Account
|
||||||
|
import app.k9mail.feature.account.common.domain.entity.SpecialFolderOption
|
||||||
|
import app.k9mail.feature.account.common.domain.entity.SpecialFolderSettings
|
||||||
|
import app.k9mail.feature.account.setup.AccountSetupExternalContract
|
||||||
|
import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator.AccountCreatorResult
|
||||||
|
import com.fsck.k9.Core
|
||||||
|
import com.fsck.k9.Preferences
|
||||||
|
import com.fsck.k9.account.DeletePolicyProvider
|
||||||
|
import com.fsck.k9.controller.MessagingController
|
||||||
|
import com.fsck.k9.mail.ServerSettings
|
||||||
|
import com.fsck.k9.mail.store.imap.ImapStoreSettings.autoDetectNamespace
|
||||||
|
import com.fsck.k9.mail.store.imap.ImapStoreSettings.createExtra
|
||||||
|
import com.fsck.k9.mail.store.imap.ImapStoreSettings.isSendClientInfo
|
||||||
|
import com.fsck.k9.mail.store.imap.ImapStoreSettings.isUseCompression
|
||||||
|
import com.fsck.k9.mail.store.imap.ImapStoreSettings.pathPrefix
|
||||||
|
import com.fsck.k9.mailstore.SpecialLocalFoldersCreator
|
||||||
|
import com.fsck.k9.preferences.UnifiedInboxConfigurator
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccount
|
||||||
|
import net.thunderbird.core.common.mail.Protocols
|
||||||
|
import net.thunderbird.core.logging.legacy.Log
|
||||||
|
import net.thunderbird.feature.account.avatar.AvatarMonogramCreator
|
||||||
|
import net.thunderbird.feature.account.storage.profile.AvatarDto
|
||||||
|
import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
|
||||||
|
import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
|
||||||
|
|
||||||
|
// TODO Move to feature/account/setup
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
internal class AccountCreator(
|
||||||
|
private val accountColorPicker: AccountColorPicker,
|
||||||
|
private val localFoldersCreator: SpecialLocalFoldersCreator,
|
||||||
|
private val preferences: Preferences,
|
||||||
|
private val context: Context,
|
||||||
|
private val messagingController: MessagingController,
|
||||||
|
private val deletePolicyProvider: DeletePolicyProvider,
|
||||||
|
private val avatarMonogramCreator: AvatarMonogramCreator,
|
||||||
|
private val unifiedInboxConfigurator: UnifiedInboxConfigurator,
|
||||||
|
private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
|
) : AccountSetupExternalContract.AccountCreator {
|
||||||
|
|
||||||
|
@Suppress("TooGenericExceptionCaught")
|
||||||
|
override suspend fun createAccount(account: Account): AccountCreatorResult {
|
||||||
|
return try {
|
||||||
|
withContext(coroutineDispatcher) { AccountCreatorResult.Success(create(account)) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(e, "Error while creating new account")
|
||||||
|
|
||||||
|
AccountCreatorResult.Error(e.message ?: "Unknown create account error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun create(account: Account): String {
|
||||||
|
val newAccount = preferences.newAccount(account.uuid)
|
||||||
|
|
||||||
|
newAccount.email = account.emailAddress
|
||||||
|
|
||||||
|
newAccount.avatar = AvatarDto(
|
||||||
|
avatarType = AvatarTypeDto.MONOGRAM,
|
||||||
|
avatarMonogram = avatarMonogramCreator.create(account.options.accountName, account.emailAddress),
|
||||||
|
avatarImageUri = null,
|
||||||
|
avatarIconName = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
newAccount.setIncomingServerSettings(account.incomingServerSettings)
|
||||||
|
newAccount.outgoingServerSettings = account.outgoingServerSettings
|
||||||
|
|
||||||
|
newAccount.oAuthState = account.authorizationState
|
||||||
|
|
||||||
|
newAccount.name = account.options.accountName
|
||||||
|
newAccount.senderName = account.options.displayName
|
||||||
|
if (account.options.emailSignature != null) {
|
||||||
|
newAccount.signatureUse = true
|
||||||
|
newAccount.signature = account.options.emailSignature
|
||||||
|
}
|
||||||
|
newAccount.isNotifyNewMail = account.options.showNotification
|
||||||
|
newAccount.automaticCheckIntervalMinutes = account.options.checkFrequencyInMinutes
|
||||||
|
newAccount.displayCount = account.options.messageDisplayCount
|
||||||
|
|
||||||
|
newAccount.deletePolicy = deletePolicyProvider.getDeletePolicy(newAccount.incomingServerSettings.type)
|
||||||
|
newAccount.chipColor = accountColorPicker.pickColor()
|
||||||
|
|
||||||
|
localFoldersCreator.createSpecialLocalFolders(newAccount)
|
||||||
|
|
||||||
|
account.specialFolderSettings?.let { specialFolderSettings ->
|
||||||
|
newAccount.setSpecialFolders(specialFolderSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
newAccount.markSetupFinished()
|
||||||
|
|
||||||
|
preferences.saveAccount(newAccount)
|
||||||
|
|
||||||
|
unifiedInboxConfigurator.configureUnifiedInbox()
|
||||||
|
|
||||||
|
Core.setServicesEnabled(context)
|
||||||
|
|
||||||
|
messagingController.refreshFolderListBlocking(newAccount)
|
||||||
|
|
||||||
|
if (account.options.checkFrequencyInMinutes == -1) {
|
||||||
|
messagingController.checkMail(newAccount, false, true, false, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newAccount.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set special folders by name.
|
||||||
|
*
|
||||||
|
* Since the folder list hasn't been synced yet, we don't have database IDs for the folders. So we use the same
|
||||||
|
* mechanism that is used when importing settings. See [com.fsck.k9.mailstore.SpecialFolderUpdater] for details.
|
||||||
|
*/
|
||||||
|
private fun LegacyAccount.setSpecialFolders(specialFolders: SpecialFolderSettings) {
|
||||||
|
importedArchiveFolder = specialFolders.archiveSpecialFolderOption.toFolderServerId()
|
||||||
|
archiveFolderSelection = specialFolders.archiveSpecialFolderOption.toFolderSelection()
|
||||||
|
|
||||||
|
importedDraftsFolder = specialFolders.draftsSpecialFolderOption.toFolderServerId()
|
||||||
|
draftsFolderSelection = specialFolders.draftsSpecialFolderOption.toFolderSelection()
|
||||||
|
|
||||||
|
importedSentFolder = specialFolders.sentSpecialFolderOption.toFolderServerId()
|
||||||
|
sentFolderSelection = specialFolders.sentSpecialFolderOption.toFolderSelection()
|
||||||
|
|
||||||
|
importedSpamFolder = specialFolders.spamSpecialFolderOption.toFolderServerId()
|
||||||
|
spamFolderSelection = specialFolders.spamSpecialFolderOption.toFolderSelection()
|
||||||
|
|
||||||
|
importedTrashFolder = specialFolders.trashSpecialFolderOption.toFolderServerId()
|
||||||
|
trashFolderSelection = specialFolders.trashSpecialFolderOption.toFolderSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SpecialFolderOption.toFolderServerId(): String? {
|
||||||
|
return when (this) {
|
||||||
|
is SpecialFolderOption.None -> null
|
||||||
|
is SpecialFolderOption.Regular -> remoteFolder.serverId.serverId
|
||||||
|
is SpecialFolderOption.Special -> remoteFolder.serverId.serverId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SpecialFolderOption.toFolderSelection(): SpecialFolderSelection {
|
||||||
|
return when (this) {
|
||||||
|
is SpecialFolderOption.None -> {
|
||||||
|
if (isAutomatic) SpecialFolderSelection.AUTOMATIC else SpecialFolderSelection.MANUAL
|
||||||
|
}
|
||||||
|
is SpecialFolderOption.Regular -> {
|
||||||
|
SpecialFolderSelection.MANUAL
|
||||||
|
}
|
||||||
|
is SpecialFolderOption.Special -> {
|
||||||
|
if (isAutomatic) SpecialFolderSelection.AUTOMATIC else SpecialFolderSelection.MANUAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LegacyAccount.setIncomingServerSettings(serverSettings: ServerSettings) {
|
||||||
|
if (serverSettings.type == Protocols.IMAP) {
|
||||||
|
useCompression = serverSettings.isUseCompression
|
||||||
|
isSendClientInfoEnabled = serverSettings.isSendClientInfo
|
||||||
|
incomingServerSettings = serverSettings.copy(
|
||||||
|
extra = createExtra(
|
||||||
|
autoDetectNamespace = serverSettings.autoDetectNamespace,
|
||||||
|
pathPrefix = serverSettings.pathPrefix,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
incomingServerSettings = serverSettings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package net.thunderbird.app.common.account
|
||||||
|
|
||||||
|
import app.k9mail.feature.account.setup.AccountSetupExternalContract
|
||||||
|
import net.thunderbird.app.common.account.data.DefaultAccountProfileLocalDataSource
|
||||||
|
import net.thunderbird.app.common.account.data.DefaultLegacyAccountWrapperManager
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccountWrapperManager
|
||||||
|
import net.thunderbird.feature.account.avatar.AvatarMonogramCreator
|
||||||
|
import net.thunderbird.feature.account.avatar.DefaultAvatarMonogramCreator
|
||||||
|
import net.thunderbird.feature.account.core.AccountCoreExternalContract.AccountProfileLocalDataSource
|
||||||
|
import net.thunderbird.feature.account.core.featureAccountCoreModule
|
||||||
|
import net.thunderbird.feature.account.storage.legacy.featureAccountStorageLegacyModule
|
||||||
|
import org.koin.android.ext.koin.androidApplication
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
internal val appCommonAccountModule = module {
|
||||||
|
includes(
|
||||||
|
featureAccountCoreModule,
|
||||||
|
featureAccountStorageLegacyModule,
|
||||||
|
)
|
||||||
|
|
||||||
|
single<LegacyAccountWrapperManager> {
|
||||||
|
DefaultLegacyAccountWrapperManager(
|
||||||
|
accountManager = get(),
|
||||||
|
accountDataMapper = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
single<AccountProfileLocalDataSource> {
|
||||||
|
DefaultAccountProfileLocalDataSource(
|
||||||
|
accountManager = get(),
|
||||||
|
dataMapper = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
single<AccountDefaultsProvider> {
|
||||||
|
DefaultAccountDefaultsProvider(
|
||||||
|
resourceProvider = get(),
|
||||||
|
featureFlagProvider = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
factory {
|
||||||
|
AccountColorPicker(
|
||||||
|
accountManager = get(),
|
||||||
|
resources = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
factory<AvatarMonogramCreator> {
|
||||||
|
DefaultAvatarMonogramCreator()
|
||||||
|
}
|
||||||
|
|
||||||
|
factory<AccountSetupExternalContract.AccountCreator> {
|
||||||
|
AccountCreator(
|
||||||
|
accountColorPicker = get(),
|
||||||
|
localFoldersCreator = get(),
|
||||||
|
preferences = get(),
|
||||||
|
context = androidApplication(),
|
||||||
|
deletePolicyProvider = get(),
|
||||||
|
messagingController = get(),
|
||||||
|
avatarMonogramCreator = get(),
|
||||||
|
unifiedInboxConfigurator = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
package net.thunderbird.app.common.account
|
||||||
|
|
||||||
|
import com.fsck.k9.CoreResourceProvider
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_MAXIMUM_AUTO_DOWNLOAD_MESSAGE_SIZE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_MESSAGE_FORMAT
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_MESSAGE_FORMAT_AUTO
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_MESSAGE_READ_RECEIPT
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_QUOTED_TEXT_SHOWN
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_QUOTE_PREFIX
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_QUOTE_STYLE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_REMOTE_SEARCH_NUM_RESULTS
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_REPLY_AFTER_QUOTE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_RINGTONE_URI
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_SORT_ASCENDING
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_SORT_TYPE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_STRIP_SIGNATURE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_SYNC_INTERVAL
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_VISIBLE_LIMIT
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.NO_OPENPGP_KEY
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.UNASSIGNED_ACCOUNT_NUMBER
|
||||||
|
import net.thunderbird.core.android.account.Expunge
|
||||||
|
import net.thunderbird.core.android.account.FolderMode
|
||||||
|
import net.thunderbird.core.android.account.Identity
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccount
|
||||||
|
import net.thunderbird.core.android.account.ShowPictures
|
||||||
|
import net.thunderbird.core.featureflag.FeatureFlagProvider
|
||||||
|
import net.thunderbird.core.featureflag.toFeatureFlagKey
|
||||||
|
import net.thunderbird.core.preference.storage.Storage
|
||||||
|
import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
|
||||||
|
import net.thunderbird.feature.notification.NotificationLight
|
||||||
|
import net.thunderbird.feature.notification.NotificationSettings
|
||||||
|
import net.thunderbird.feature.notification.NotificationVibration
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
internal class DefaultAccountDefaultsProvider(
|
||||||
|
private val resourceProvider: CoreResourceProvider,
|
||||||
|
private val featureFlagProvider: FeatureFlagProvider,
|
||||||
|
) : AccountDefaultsProvider {
|
||||||
|
|
||||||
|
override fun applyDefaults(account: LegacyAccount) = with(account) {
|
||||||
|
applyLegacyDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyOverwrites(account: LegacyAccount, storage: Storage) = with(account) {
|
||||||
|
if (storage.contains("${account.uuid}.notifyNewMail")) {
|
||||||
|
isNotifyNewMail = storage.getBoolean("${account.uuid}.notifyNewMail", false)
|
||||||
|
isNotifySelfNewMail = storage.getBoolean("${account.uuid}.notifySelfNewMail", true)
|
||||||
|
} else {
|
||||||
|
isNotifyNewMail = featureFlagProvider.provide(
|
||||||
|
"email_notification_default".toFeatureFlagKey(),
|
||||||
|
).whenEnabledOrNot(
|
||||||
|
onEnabled = { true },
|
||||||
|
onDisabledOrUnavailable = { false },
|
||||||
|
)
|
||||||
|
|
||||||
|
isNotifySelfNewMail = featureFlagProvider.provide(
|
||||||
|
"email_notification_default".toFeatureFlagKey(),
|
||||||
|
).whenEnabledOrNot(
|
||||||
|
onEnabled = { true },
|
||||||
|
onDisabledOrUnavailable = { false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
private fun LegacyAccount.applyLegacyDefaults() {
|
||||||
|
automaticCheckIntervalMinutes = DEFAULT_SYNC_INTERVAL
|
||||||
|
idleRefreshMinutes = 24
|
||||||
|
displayCount = DEFAULT_VISIBLE_LIMIT
|
||||||
|
accountNumber = UNASSIGNED_ACCOUNT_NUMBER
|
||||||
|
isNotifyNewMail = true
|
||||||
|
folderNotifyNewMailMode = FolderMode.ALL
|
||||||
|
isNotifySync = false
|
||||||
|
isNotifySelfNewMail = true
|
||||||
|
isNotifyContactsMailOnly = false
|
||||||
|
isIgnoreChatMessages = false
|
||||||
|
messagesNotificationChannelVersion = 0
|
||||||
|
folderDisplayMode = FolderMode.NOT_SECOND_CLASS
|
||||||
|
folderSyncMode = FolderMode.FIRST_CLASS
|
||||||
|
folderPushMode = FolderMode.NONE
|
||||||
|
sortType = DEFAULT_SORT_TYPE
|
||||||
|
setSortAscending(DEFAULT_SORT_TYPE, DEFAULT_SORT_ASCENDING)
|
||||||
|
showPictures = ShowPictures.NEVER
|
||||||
|
isSignatureBeforeQuotedText = false
|
||||||
|
expungePolicy = Expunge.EXPUNGE_IMMEDIATELY
|
||||||
|
importedAutoExpandFolder = null
|
||||||
|
legacyInboxFolder = null
|
||||||
|
maxPushFolders = 10
|
||||||
|
isSubscribedFoldersOnly = false
|
||||||
|
maximumPolledMessageAge = -1
|
||||||
|
maximumAutoDownloadMessageSize = DEFAULT_MAXIMUM_AUTO_DOWNLOAD_MESSAGE_SIZE
|
||||||
|
messageFormat = DEFAULT_MESSAGE_FORMAT
|
||||||
|
isMessageFormatAuto = DEFAULT_MESSAGE_FORMAT_AUTO
|
||||||
|
isMessageReadReceipt = DEFAULT_MESSAGE_READ_RECEIPT
|
||||||
|
quoteStyle = DEFAULT_QUOTE_STYLE
|
||||||
|
quotePrefix = DEFAULT_QUOTE_PREFIX
|
||||||
|
isDefaultQuotedTextShown = DEFAULT_QUOTED_TEXT_SHOWN
|
||||||
|
isReplyAfterQuote = DEFAULT_REPLY_AFTER_QUOTE
|
||||||
|
isStripSignature = DEFAULT_STRIP_SIGNATURE
|
||||||
|
isSyncRemoteDeletions = true
|
||||||
|
openPgpKey = NO_OPENPGP_KEY
|
||||||
|
isRemoteSearchFullText = false
|
||||||
|
remoteSearchNumResults = DEFAULT_REMOTE_SEARCH_NUM_RESULTS
|
||||||
|
isUploadSentMessages = true
|
||||||
|
isMarkMessageAsReadOnView = true
|
||||||
|
isMarkMessageAsReadOnDelete = true
|
||||||
|
isAlwaysShowCcBcc = false
|
||||||
|
lastSyncTime = 0L
|
||||||
|
lastFolderListRefreshTime = 0L
|
||||||
|
|
||||||
|
setArchiveFolderId(null, SpecialFolderSelection.AUTOMATIC)
|
||||||
|
setDraftsFolderId(null, SpecialFolderSelection.AUTOMATIC)
|
||||||
|
setSentFolderId(null, SpecialFolderSelection.AUTOMATIC)
|
||||||
|
setSpamFolderId(null, SpecialFolderSelection.AUTOMATIC)
|
||||||
|
setTrashFolderId(null, SpecialFolderSelection.AUTOMATIC)
|
||||||
|
|
||||||
|
identities = ArrayList<Identity>()
|
||||||
|
|
||||||
|
val identity = Identity(
|
||||||
|
signatureUse = false,
|
||||||
|
signature = null,
|
||||||
|
description = resourceProvider.defaultIdentityDescription(),
|
||||||
|
)
|
||||||
|
identities.add(identity)
|
||||||
|
|
||||||
|
updateNotificationSettings {
|
||||||
|
NotificationSettings(
|
||||||
|
isRingEnabled = true,
|
||||||
|
ringtone = DEFAULT_RINGTONE_URI,
|
||||||
|
light = NotificationLight.Disabled,
|
||||||
|
vibration = NotificationVibration.DEFAULT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetChangeMarkers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package net.thunderbird.app.common.account.data
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccountWrapperManager
|
||||||
|
import net.thunderbird.feature.account.AccountId
|
||||||
|
import net.thunderbird.feature.account.core.AccountCoreExternalContract.AccountProfileLocalDataSource
|
||||||
|
import net.thunderbird.feature.account.profile.AccountProfile
|
||||||
|
import net.thunderbird.feature.account.storage.mapper.AccountProfileDataMapper
|
||||||
|
|
||||||
|
internal class DefaultAccountProfileLocalDataSource(
|
||||||
|
private val accountManager: LegacyAccountWrapperManager,
|
||||||
|
private val dataMapper: AccountProfileDataMapper,
|
||||||
|
) : AccountProfileLocalDataSource {
|
||||||
|
|
||||||
|
override fun getById(accountId: AccountId): Flow<AccountProfile?> {
|
||||||
|
return accountManager.getById(accountId)
|
||||||
|
.map { account ->
|
||||||
|
account?.let { dto ->
|
||||||
|
dataMapper.toDomain(dto.profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(accountProfile: AccountProfile) {
|
||||||
|
val currentAccount = accountManager.getById(accountProfile.id)
|
||||||
|
.firstOrNull() ?: return
|
||||||
|
|
||||||
|
val accountProfile = dataMapper.toDto(accountProfile)
|
||||||
|
|
||||||
|
val updatedAccount = currentAccount.copy(
|
||||||
|
profile = accountProfile,
|
||||||
|
)
|
||||||
|
|
||||||
|
accountManager.update(updatedAccount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package net.thunderbird.app.common.account.data
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import net.thunderbird.core.android.account.AccountManager
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccountWrapper
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccountWrapperManager
|
||||||
|
import net.thunderbird.feature.account.AccountId
|
||||||
|
import net.thunderbird.feature.account.storage.legacy.mapper.DefaultLegacyAccountWrapperDataMapper
|
||||||
|
|
||||||
|
internal class DefaultLegacyAccountWrapperManager(
|
||||||
|
private val accountManager: AccountManager,
|
||||||
|
private val accountDataMapper: DefaultLegacyAccountWrapperDataMapper,
|
||||||
|
) : LegacyAccountWrapperManager {
|
||||||
|
|
||||||
|
override fun getAll(): Flow<List<LegacyAccountWrapper>> {
|
||||||
|
return accountManager.getAccountsFlow()
|
||||||
|
.map { list ->
|
||||||
|
list.map { account ->
|
||||||
|
accountDataMapper.toDomain(account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getById(id: AccountId): Flow<LegacyAccountWrapper?> {
|
||||||
|
return accountManager.getAccountFlow(id.asRaw()).map { account ->
|
||||||
|
account?.let {
|
||||||
|
accountDataMapper.toDomain(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(account: LegacyAccountWrapper) {
|
||||||
|
accountManager.saveAccount(
|
||||||
|
accountDataMapper.toDto(account),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package net.thunderbird.app.common.core
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
import net.thunderbird.app.common.core.logging.DefaultLogLevelManager
|
||||||
|
import net.thunderbird.core.common.inject.getList
|
||||||
|
import net.thunderbird.core.common.inject.singleListOf
|
||||||
|
import net.thunderbird.core.logging.DefaultLogger
|
||||||
|
import net.thunderbird.core.logging.LogLevel
|
||||||
|
import net.thunderbird.core.logging.LogLevelManager
|
||||||
|
import net.thunderbird.core.logging.LogLevelProvider
|
||||||
|
import net.thunderbird.core.logging.LogSink
|
||||||
|
import net.thunderbird.core.logging.Logger
|
||||||
|
import net.thunderbird.core.logging.composite.CompositeLogSink
|
||||||
|
import net.thunderbird.core.logging.console.ConsoleLogSink
|
||||||
|
import net.thunderbird.core.logging.file.AndroidFileSystemManager
|
||||||
|
import net.thunderbird.core.logging.file.FileLogSink
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.dsl.bind
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val appCommonCoreModule: Module = module {
|
||||||
|
single<LogLevelManager> {
|
||||||
|
DefaultLogLevelManager()
|
||||||
|
}.bind<LogLevelProvider>()
|
||||||
|
|
||||||
|
singleListOf<LogSink>(
|
||||||
|
{ ConsoleLogSink(level = LogLevel.VERBOSE) },
|
||||||
|
)
|
||||||
|
|
||||||
|
single<CompositeLogSink> {
|
||||||
|
CompositeLogSink(
|
||||||
|
logLevelProvider = get(),
|
||||||
|
sinks = getList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
single<Logger> {
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
|
DefaultLogger(
|
||||||
|
sink = get<CompositeLogSink>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
single<CompositeLogSink>(named(SYNC_DEBUG_LOG)) {
|
||||||
|
CompositeLogSink(
|
||||||
|
logLevelProvider = get(),
|
||||||
|
sinks = getList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
single<FileLogSink>(named(SYNC_DEBUG_LOG)) {
|
||||||
|
FileLogSink(
|
||||||
|
level = LogLevel.DEBUG,
|
||||||
|
fileName = "thunderbird-sync-debug",
|
||||||
|
fileLocation = get<Context>().filesDir.path,
|
||||||
|
fileSystemManager = AndroidFileSystemManager(get<Context>().contentResolver),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
single<Logger>(named(SYNC_DEBUG_LOG)) {
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
|
DefaultLogger(
|
||||||
|
sink = get<CompositeLogSink>(named(SYNC_DEBUG_LOG)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal const val SYNC_DEBUG_LOG = "syncDebug"
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package net.thunderbird.app.common.core.logging
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import net.thunderbird.app.common.BuildConfig
|
||||||
|
import net.thunderbird.core.logging.LogLevel
|
||||||
|
import net.thunderbird.core.logging.LogLevelManager
|
||||||
|
|
||||||
|
class DefaultLogLevelManager : LogLevelManager {
|
||||||
|
private val defaultLevel = if (BuildConfig.DEBUG) LogLevel.VERBOSE else LogLevel.INFO
|
||||||
|
private val logLevel = MutableStateFlow(defaultLevel)
|
||||||
|
|
||||||
|
override fun override(level: LogLevel) {
|
||||||
|
logLevel.update { level }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun restoreDefault() {
|
||||||
|
override(defaultLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun current(): LogLevel = logLevel.value
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package net.thunderbird.app.common.feature
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import app.k9mail.feature.launcher.FeatureLauncherExternalContract
|
||||||
|
import com.fsck.k9.activity.MessageList
|
||||||
|
|
||||||
|
internal class AccountSetupFinishedLauncher(
|
||||||
|
private val context: Context,
|
||||||
|
) : FeatureLauncherExternalContract.AccountSetupFinishedLauncher {
|
||||||
|
override fun launch(accountUuid: String?) {
|
||||||
|
if (accountUuid != null) {
|
||||||
|
MessageList.launch(context, accountUuid)
|
||||||
|
} else {
|
||||||
|
MessageList.launch(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package net.thunderbird.app.common.feature
|
||||||
|
|
||||||
|
import app.k9mail.feature.launcher.FeatureLauncherExternalContract
|
||||||
|
import app.k9mail.feature.launcher.di.featureLauncherModule
|
||||||
|
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract
|
||||||
|
import net.thunderbird.feature.notification.impl.inject.featureNotificationModule
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
internal val appCommonFeatureModule = module {
|
||||||
|
includes(featureLauncherModule)
|
||||||
|
includes(featureNotificationModule)
|
||||||
|
|
||||||
|
factory<FeatureLauncherExternalContract.AccountSetupFinishedLauncher> {
|
||||||
|
AccountSetupFinishedLauncher(
|
||||||
|
context = androidContext(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
single<NavigationDrawerExternalContract.DrawerConfigLoader> {
|
||||||
|
NavigationDrawerConfigLoader(get())
|
||||||
|
}
|
||||||
|
|
||||||
|
single<NavigationDrawerExternalContract.DrawerConfigWriter> {
|
||||||
|
NavigationDrawerConfigWriter(get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package net.thunderbird.app.common.feature
|
||||||
|
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import net.thunderbird.core.logging.file.FileLogSink
|
||||||
|
|
||||||
|
class LoggerLifecycleObserver(val fileLogSink: FileLogSink?) : DefaultLifecycleObserver {
|
||||||
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
|
super.onStop(owner)
|
||||||
|
fileLogSink?.let {
|
||||||
|
owner.lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
it.flushAndCloseBuffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package net.thunderbird.app.common.feature
|
||||||
|
|
||||||
|
import com.fsck.k9.preferences.DrawerConfigManager
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract
|
||||||
|
|
||||||
|
internal class NavigationDrawerConfigLoader(private val drawerConfigManager: DrawerConfigManager) :
|
||||||
|
NavigationDrawerExternalContract.DrawerConfigLoader {
|
||||||
|
override fun loadDrawerConfigFlow(): Flow<NavigationDrawerExternalContract.DrawerConfig> {
|
||||||
|
return drawerConfigManager.getConfigFlow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package net.thunderbird.app.common.feature
|
||||||
|
|
||||||
|
import com.fsck.k9.preferences.DrawerConfigManager
|
||||||
|
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract
|
||||||
|
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract.DrawerConfig
|
||||||
|
|
||||||
|
internal class NavigationDrawerConfigWriter(
|
||||||
|
private val drawerConfigManager: DrawerConfigManager,
|
||||||
|
) : NavigationDrawerExternalContract.DrawerConfigWriter {
|
||||||
|
override fun writeDrawerConfig(drawerConfig: DrawerConfig) {
|
||||||
|
drawerConfigManager.save(drawerConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
14
app-common/src/main/res/xml/network_security_config.xml
Normal file
14
app-common/src/main/res/xml/network_security_config.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:ignore="InsecureBaseConfiguration,AcceptsUserCertificates"
|
||||||
|
>
|
||||||
|
|
||||||
|
<base-config cleartextTrafficPermitted="true">
|
||||||
|
<trust-anchors>
|
||||||
|
<certificates src="system" />
|
||||||
|
<certificates src="user" />
|
||||||
|
</trust-anchors>
|
||||||
|
</base-config>
|
||||||
|
|
||||||
|
</network-security-config>
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
package net.thunderbird.app.common.account
|
||||||
|
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import assertk.assertions.isFalse
|
||||||
|
import assertk.assertions.isNull
|
||||||
|
import assertk.assertions.isTrue
|
||||||
|
import com.fsck.k9.CoreResourceProvider
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_MAXIMUM_AUTO_DOWNLOAD_MESSAGE_SIZE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_MESSAGE_FORMAT
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_MESSAGE_FORMAT_AUTO
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_MESSAGE_READ_RECEIPT
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_QUOTED_TEXT_SHOWN
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_QUOTE_PREFIX
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_QUOTE_STYLE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_REMOTE_SEARCH_NUM_RESULTS
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_REPLY_AFTER_QUOTE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_RINGTONE_URI
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_SORT_ASCENDING
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_SORT_TYPE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_STRIP_SIGNATURE
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_SYNC_INTERVAL
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.DEFAULT_VISIBLE_LIMIT
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.NO_OPENPGP_KEY
|
||||||
|
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.UNASSIGNED_ACCOUNT_NUMBER
|
||||||
|
import net.thunderbird.core.android.account.Expunge
|
||||||
|
import net.thunderbird.core.android.account.FolderMode
|
||||||
|
import net.thunderbird.core.android.account.Identity
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccount
|
||||||
|
import net.thunderbird.core.android.account.ShowPictures
|
||||||
|
import net.thunderbird.core.featureflag.FeatureFlagResult
|
||||||
|
import net.thunderbird.core.preference.storage.Storage
|
||||||
|
import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
|
||||||
|
import net.thunderbird.feature.notification.NotificationLight
|
||||||
|
import net.thunderbird.feature.notification.NotificationSettings
|
||||||
|
import net.thunderbird.feature.notification.NotificationVibration
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
|
||||||
|
class DefaultAccountDefaultsProviderTest {
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
@Test
|
||||||
|
fun `applyDefaults should return default values`() {
|
||||||
|
// arrange
|
||||||
|
val resourceProvider = mock<CoreResourceProvider> {
|
||||||
|
on { defaultIdentityDescription() } doReturn "Default Identity"
|
||||||
|
}
|
||||||
|
val account = LegacyAccount(
|
||||||
|
uuid = "cf728064-077d-4369-a0c7-7c2b21693d9b",
|
||||||
|
isSensitiveDebugLoggingEnabled = { false },
|
||||||
|
)
|
||||||
|
val identities = listOf(
|
||||||
|
Identity(
|
||||||
|
signatureUse = false,
|
||||||
|
signature = null,
|
||||||
|
description = resourceProvider.defaultIdentityDescription(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val notificationSettings = NotificationSettings(
|
||||||
|
isRingEnabled = true,
|
||||||
|
ringtone = DEFAULT_RINGTONE_URI,
|
||||||
|
light = NotificationLight.Disabled,
|
||||||
|
vibration = NotificationVibration.DEFAULT,
|
||||||
|
)
|
||||||
|
val testSubject = DefaultAccountDefaultsProvider(
|
||||||
|
resourceProvider = resourceProvider,
|
||||||
|
featureFlagProvider = {
|
||||||
|
FeatureFlagResult.Disabled
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// act
|
||||||
|
testSubject.applyDefaults(account)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
assertThat(account.automaticCheckIntervalMinutes).isEqualTo(DEFAULT_SYNC_INTERVAL)
|
||||||
|
assertThat(account.idleRefreshMinutes).isEqualTo(24)
|
||||||
|
assertThat(account.displayCount).isEqualTo(DEFAULT_VISIBLE_LIMIT)
|
||||||
|
assertThat(account.accountNumber).isEqualTo(UNASSIGNED_ACCOUNT_NUMBER)
|
||||||
|
assertThat(account.isNotifyNewMail).isTrue()
|
||||||
|
assertThat(account.folderNotifyNewMailMode).isEqualTo(FolderMode.ALL)
|
||||||
|
assertThat(account.isNotifySync).isFalse()
|
||||||
|
assertThat(account.isNotifySelfNewMail).isTrue()
|
||||||
|
assertThat(account.isNotifyContactsMailOnly).isFalse()
|
||||||
|
assertThat(account.isIgnoreChatMessages).isFalse()
|
||||||
|
assertThat(account.messagesNotificationChannelVersion).isEqualTo(0)
|
||||||
|
assertThat(account.folderDisplayMode).isEqualTo(FolderMode.NOT_SECOND_CLASS)
|
||||||
|
assertThat(account.folderSyncMode).isEqualTo(FolderMode.FIRST_CLASS)
|
||||||
|
assertThat(account.folderPushMode).isEqualTo(FolderMode.NONE)
|
||||||
|
assertThat(account.sortType).isEqualTo(DEFAULT_SORT_TYPE)
|
||||||
|
assertThat(account.isSortAscending(DEFAULT_SORT_TYPE)).isEqualTo(DEFAULT_SORT_ASCENDING)
|
||||||
|
assertThat(account.showPictures).isEqualTo(ShowPictures.NEVER)
|
||||||
|
assertThat(account.isSignatureBeforeQuotedText).isFalse()
|
||||||
|
assertThat(account.expungePolicy).isEqualTo(Expunge.EXPUNGE_IMMEDIATELY)
|
||||||
|
assertThat(account.importedAutoExpandFolder).isNull()
|
||||||
|
assertThat(account.legacyInboxFolder).isNull()
|
||||||
|
assertThat(account.maxPushFolders).isEqualTo(10)
|
||||||
|
assertThat(account.isSubscribedFoldersOnly).isFalse()
|
||||||
|
assertThat(account.maximumPolledMessageAge).isEqualTo(-1)
|
||||||
|
assertThat(account.maximumAutoDownloadMessageSize).isEqualTo(DEFAULT_MAXIMUM_AUTO_DOWNLOAD_MESSAGE_SIZE)
|
||||||
|
assertThat(account.messageFormat).isEqualTo(DEFAULT_MESSAGE_FORMAT)
|
||||||
|
assertThat(account.isMessageFormatAuto).isEqualTo(DEFAULT_MESSAGE_FORMAT_AUTO)
|
||||||
|
assertThat(account.isMessageReadReceipt).isEqualTo(DEFAULT_MESSAGE_READ_RECEIPT)
|
||||||
|
assertThat(account.quoteStyle).isEqualTo(DEFAULT_QUOTE_STYLE)
|
||||||
|
assertThat(account.quotePrefix).isEqualTo(DEFAULT_QUOTE_PREFIX)
|
||||||
|
assertThat(account.isDefaultQuotedTextShown).isEqualTo(DEFAULT_QUOTED_TEXT_SHOWN)
|
||||||
|
assertThat(account.isReplyAfterQuote).isEqualTo(DEFAULT_REPLY_AFTER_QUOTE)
|
||||||
|
assertThat(account.isStripSignature).isEqualTo(DEFAULT_STRIP_SIGNATURE)
|
||||||
|
assertThat(account.isSyncRemoteDeletions).isTrue()
|
||||||
|
assertThat(account.openPgpKey).isEqualTo(NO_OPENPGP_KEY)
|
||||||
|
assertThat(account.isRemoteSearchFullText).isFalse()
|
||||||
|
assertThat(account.remoteSearchNumResults).isEqualTo(DEFAULT_REMOTE_SEARCH_NUM_RESULTS)
|
||||||
|
assertThat(account.isUploadSentMessages).isTrue()
|
||||||
|
assertThat(account.isMarkMessageAsReadOnView).isTrue()
|
||||||
|
assertThat(account.isMarkMessageAsReadOnDelete).isTrue()
|
||||||
|
assertThat(account.isAlwaysShowCcBcc).isFalse()
|
||||||
|
assertThat(account.lastSyncTime).isEqualTo(0L)
|
||||||
|
assertThat(account.lastFolderListRefreshTime).isEqualTo(0L)
|
||||||
|
|
||||||
|
assertThat(account.archiveFolderId).isNull()
|
||||||
|
assertThat(account.archiveFolderSelection).isEqualTo(SpecialFolderSelection.AUTOMATIC)
|
||||||
|
assertThat(account.draftsFolderId).isNull()
|
||||||
|
assertThat(account.draftsFolderSelection).isEqualTo(SpecialFolderSelection.AUTOMATIC)
|
||||||
|
assertThat(account.sentFolderId).isNull()
|
||||||
|
assertThat(account.sentFolderSelection).isEqualTo(SpecialFolderSelection.AUTOMATIC)
|
||||||
|
assertThat(account.spamFolderId).isNull()
|
||||||
|
assertThat(account.spamFolderSelection).isEqualTo(SpecialFolderSelection.AUTOMATIC)
|
||||||
|
assertThat(account.trashFolderId).isNull()
|
||||||
|
assertThat(account.trashFolderSelection).isEqualTo(SpecialFolderSelection.AUTOMATIC)
|
||||||
|
assertThat(account.archiveFolderId).isNull()
|
||||||
|
assertThat(account.archiveFolderSelection).isEqualTo(SpecialFolderSelection.AUTOMATIC)
|
||||||
|
|
||||||
|
assertThat(account.identities).isEqualTo(identities)
|
||||||
|
assertThat(account.notificationSettings).isEqualTo(notificationSettings)
|
||||||
|
|
||||||
|
assertThat(account.isChangedVisibleLimits).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `applyOverwrites should return patched account when disabled`() {
|
||||||
|
// arrange
|
||||||
|
val resourceProvider = mock<CoreResourceProvider> {
|
||||||
|
on { defaultIdentityDescription() } doReturn "Default Identity"
|
||||||
|
}
|
||||||
|
val account = LegacyAccount(
|
||||||
|
uuid = "cf728064-077d-4369-a0c7-7c2b21693d9b",
|
||||||
|
isSensitiveDebugLoggingEnabled = { false },
|
||||||
|
)
|
||||||
|
val storage = mock<Storage> {
|
||||||
|
on { contains("${account.uuid}.notifyNewMail") } doReturn false
|
||||||
|
on { getBoolean("${account.uuid}.notifyNewMail", false) } doReturn false
|
||||||
|
on { getBoolean("${account.uuid}.notifySelfNewMail", false) } doReturn false
|
||||||
|
}
|
||||||
|
val testSubject = DefaultAccountDefaultsProvider(
|
||||||
|
resourceProvider = resourceProvider,
|
||||||
|
featureFlagProvider = {
|
||||||
|
FeatureFlagResult.Disabled
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// act
|
||||||
|
testSubject.applyOverwrites(account, storage)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
assertThat(account.isNotifyNewMail).isFalse()
|
||||||
|
assertThat(account.isNotifySelfNewMail).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `applyOverwrites should return patched account when enabled`() {
|
||||||
|
// arrange
|
||||||
|
val resourceProvider = mock<CoreResourceProvider> {
|
||||||
|
on { defaultIdentityDescription() } doReturn "Default Identity"
|
||||||
|
}
|
||||||
|
val account = LegacyAccount(
|
||||||
|
uuid = "cf728064-077d-4369-a0c7-7c2b21693d9b",
|
||||||
|
isSensitiveDebugLoggingEnabled = { false },
|
||||||
|
)
|
||||||
|
val storage = mock<Storage> {
|
||||||
|
on { contains("${account.uuid}.notifyNewMail") } doReturn false
|
||||||
|
on { getBoolean("${account.uuid}.notifyNewMail", false) } doReturn false
|
||||||
|
on { getBoolean("${account.uuid}.notifySelfNewMail", false) } doReturn false
|
||||||
|
}
|
||||||
|
val testSubject = DefaultAccountDefaultsProvider(
|
||||||
|
resourceProvider = resourceProvider,
|
||||||
|
featureFlagProvider = {
|
||||||
|
FeatureFlagResult.Enabled
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// act
|
||||||
|
testSubject.applyOverwrites(account, storage)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
assertThat(account.isNotifyNewMail).isTrue()
|
||||||
|
assertThat(account.isNotifySelfNewMail).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `applyOverwrites updates account notification values from storage when storage contains isNotifyNewMail value`() {
|
||||||
|
// arrange
|
||||||
|
val resourceProvider = mock<CoreResourceProvider> {
|
||||||
|
on { defaultIdentityDescription() } doReturn "Default Identity"
|
||||||
|
}
|
||||||
|
val account = LegacyAccount(
|
||||||
|
uuid = "cf728064-077d-4369-a0c7-7c2b21693d9b",
|
||||||
|
isSensitiveDebugLoggingEnabled = { false },
|
||||||
|
)
|
||||||
|
val storage = mock<Storage> {
|
||||||
|
on { contains("${account.uuid}.notifyNewMail") } doReturn true
|
||||||
|
on { getBoolean("${account.uuid}.notifyNewMail", false) } doReturn false
|
||||||
|
on { getBoolean("${account.uuid}.notifySelfNewMail", false) } doReturn false
|
||||||
|
}
|
||||||
|
val testSubject = DefaultAccountDefaultsProvider(
|
||||||
|
resourceProvider = resourceProvider,
|
||||||
|
featureFlagProvider = {
|
||||||
|
FeatureFlagResult.Enabled
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// act
|
||||||
|
testSubject.applyOverwrites(account, storage)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
assertThat(account.isNotifyNewMail).isFalse()
|
||||||
|
assertThat(account.isNotifySelfNewMail).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `applyOverwrites updates account notification values from featureFlag values when storage does not contain isNotifyNewMail value`() {
|
||||||
|
// arrange
|
||||||
|
val resourceProvider = mock<CoreResourceProvider> {
|
||||||
|
on { defaultIdentityDescription() } doReturn "Default Identity"
|
||||||
|
}
|
||||||
|
val account = LegacyAccount(
|
||||||
|
uuid = "cf728064-077d-4369-a0c7-7c2b21693d9b",
|
||||||
|
isSensitiveDebugLoggingEnabled = { false },
|
||||||
|
)
|
||||||
|
val storage = mock<Storage> {
|
||||||
|
on { contains("${account.uuid}.notifyNewMail") } doReturn false
|
||||||
|
on { getBoolean("${account.uuid}.notifyNewMail", false) } doReturn false
|
||||||
|
on { getBoolean("${account.uuid}.notifySelfNewMail", false) } doReturn false
|
||||||
|
}
|
||||||
|
val testSubject = DefaultAccountDefaultsProvider(
|
||||||
|
resourceProvider = resourceProvider,
|
||||||
|
featureFlagProvider = {
|
||||||
|
FeatureFlagResult.Enabled
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// act
|
||||||
|
testSubject.applyOverwrites(account, storage)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
assertThat(account.isNotifyNewMail).isTrue()
|
||||||
|
assertThat(account.isNotifySelfNewMail).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
package net.thunderbird.app.common.account.data
|
||||||
|
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import com.fsck.k9.mail.AuthType
|
||||||
|
import com.fsck.k9.mail.ConnectionSecurity
|
||||||
|
import com.fsck.k9.mail.ServerSettings
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.thunderbird.account.fake.FakeAccountProfileData.PROFILE_COLOR
|
||||||
|
import net.thunderbird.account.fake.FakeAccountProfileData.PROFILE_NAME
|
||||||
|
import net.thunderbird.core.android.account.Identity
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccountWrapper
|
||||||
|
import net.thunderbird.feature.account.AccountId
|
||||||
|
import net.thunderbird.feature.account.AccountIdFactory
|
||||||
|
import net.thunderbird.feature.account.profile.AccountAvatar
|
||||||
|
import net.thunderbird.feature.account.profile.AccountProfile
|
||||||
|
import net.thunderbird.feature.account.storage.legacy.mapper.DefaultAccountAvatarDataMapper
|
||||||
|
import net.thunderbird.feature.account.storage.legacy.mapper.DefaultAccountProfileDataMapper
|
||||||
|
import net.thunderbird.feature.account.storage.profile.AvatarDto
|
||||||
|
import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
|
||||||
|
import net.thunderbird.feature.account.storage.profile.ProfileDto
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class DefaultAccountProfileLocalDataSourceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getById should return account profile`() = runTest {
|
||||||
|
// arrange
|
||||||
|
val accountId = AccountIdFactory.create()
|
||||||
|
val legacyAccount = createLegacyAccount(accountId)
|
||||||
|
val accountProfile = createAccountProfile(accountId)
|
||||||
|
val testSubject = createTestSubject(legacyAccount)
|
||||||
|
|
||||||
|
// act & assert
|
||||||
|
testSubject.getById(accountId).test {
|
||||||
|
assertThat(awaitItem()).isEqualTo(accountProfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getById should return null when account is not found`() = runTest {
|
||||||
|
// arrange
|
||||||
|
val accountId = AccountIdFactory.create()
|
||||||
|
val testSubject = createTestSubject(null)
|
||||||
|
|
||||||
|
// act & assert
|
||||||
|
testSubject.getById(accountId).test {
|
||||||
|
assertThat(awaitItem()).isEqualTo(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `update should save account profile`() = runTest {
|
||||||
|
// arrange
|
||||||
|
val accountId = AccountIdFactory.create()
|
||||||
|
val legacyAccount = createLegacyAccount(accountId)
|
||||||
|
val accountProfile = createAccountProfile(accountId)
|
||||||
|
|
||||||
|
val updatedName = "updatedName"
|
||||||
|
val updatedAccountProfile = accountProfile.copy(name = updatedName)
|
||||||
|
|
||||||
|
val testSubject = createTestSubject(legacyAccount)
|
||||||
|
|
||||||
|
// act & assert
|
||||||
|
testSubject.getById(accountId).test {
|
||||||
|
assertThat(awaitItem()).isEqualTo(accountProfile)
|
||||||
|
|
||||||
|
testSubject.update(updatedAccountProfile)
|
||||||
|
|
||||||
|
assertThat(awaitItem()).isEqualTo(updatedAccountProfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object Companion {
|
||||||
|
fun createLegacyAccount(
|
||||||
|
id: AccountId,
|
||||||
|
displayName: String = PROFILE_NAME,
|
||||||
|
color: Int = PROFILE_COLOR,
|
||||||
|
): LegacyAccountWrapper {
|
||||||
|
return LegacyAccountWrapper(
|
||||||
|
isSensitiveDebugLoggingEnabled = { true },
|
||||||
|
id = id,
|
||||||
|
name = displayName,
|
||||||
|
email = "demo@example.com",
|
||||||
|
profile = ProfileDto(
|
||||||
|
id = id,
|
||||||
|
name = displayName,
|
||||||
|
color = color,
|
||||||
|
avatar = AvatarDto(
|
||||||
|
avatarType = AvatarTypeDto.ICON,
|
||||||
|
avatarMonogram = null,
|
||||||
|
avatarImageUri = null,
|
||||||
|
avatarIconName = "star",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
identities = listOf(
|
||||||
|
Identity(
|
||||||
|
signatureUse = false,
|
||||||
|
description = "Demo User",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
incomingServerSettings = ServerSettings(
|
||||||
|
type = "imap",
|
||||||
|
host = "imap.example.com",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED,
|
||||||
|
authenticationType = AuthType.PLAIN,
|
||||||
|
username = "test",
|
||||||
|
password = "password",
|
||||||
|
clientCertificateAlias = null,
|
||||||
|
),
|
||||||
|
outgoingServerSettings = ServerSettings(
|
||||||
|
type = "smtp",
|
||||||
|
host = "smtp.example.com",
|
||||||
|
port = 465,
|
||||||
|
connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED,
|
||||||
|
authenticationType = AuthType.PLAIN,
|
||||||
|
username = "test",
|
||||||
|
password = "password",
|
||||||
|
clientCertificateAlias = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAccountProfile(
|
||||||
|
accountId: AccountId,
|
||||||
|
name: String = PROFILE_NAME,
|
||||||
|
color: Int = PROFILE_COLOR,
|
||||||
|
): AccountProfile {
|
||||||
|
return AccountProfile(
|
||||||
|
id = accountId,
|
||||||
|
name = name,
|
||||||
|
color = color,
|
||||||
|
avatar = AccountAvatar.Icon(
|
||||||
|
name = "star",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTestSubject(
|
||||||
|
legacyAccount: LegacyAccountWrapper?,
|
||||||
|
): DefaultAccountProfileLocalDataSource {
|
||||||
|
return DefaultAccountProfileLocalDataSource(
|
||||||
|
accountManager = FakeLegacyAccountWrapperManager(
|
||||||
|
initialAccounts = if (legacyAccount != null) {
|
||||||
|
listOf(legacyAccount)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
dataMapper = DefaultAccountProfileDataMapper(
|
||||||
|
avatarMapper = DefaultAccountAvatarDataMapper(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package net.thunderbird.app.common.account.data
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccountWrapper
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccountWrapperManager
|
||||||
|
import net.thunderbird.feature.account.AccountId
|
||||||
|
|
||||||
|
internal class FakeLegacyAccountWrapperManager(
|
||||||
|
initialAccounts: List<LegacyAccountWrapper> = emptyList(),
|
||||||
|
) : LegacyAccountWrapperManager {
|
||||||
|
|
||||||
|
private val accountsState = MutableStateFlow(
|
||||||
|
initialAccounts,
|
||||||
|
)
|
||||||
|
private val accounts: StateFlow<List<LegacyAccountWrapper>> = accountsState
|
||||||
|
|
||||||
|
override fun getAll(): Flow<List<LegacyAccountWrapper>> = accounts
|
||||||
|
|
||||||
|
override fun getById(id: AccountId): Flow<LegacyAccountWrapper?> = accounts
|
||||||
|
.map { list ->
|
||||||
|
list.find { it.id == id }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(account: LegacyAccountWrapper) {
|
||||||
|
accountsState.update { currentList ->
|
||||||
|
currentList.toMutableList().apply {
|
||||||
|
removeIf { it.uuid == account.uuid }
|
||||||
|
add(account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
app-k9mail/README.md
Normal file
12
app-k9mail/README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# K-9 Mail
|
||||||
|
|
||||||
|
This is the source code repository for the K-9 Mail project.
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
### F-Droid
|
||||||
|
|
||||||
|
K-9 Mail is available on F-Droid. The apps metadata for F-Droid is available within the [metadata](fastlane/metadata)
|
||||||
|
folder. The metadata is setup according to
|
||||||
|
the [All About Descriptions, Graphics, and Screenshots](https://f-droid.org/en/docs/All_About_Descriptions_Graphics_and_Screenshots/)
|
||||||
|
and [Build Metadata Reference](https://f-droid.org/en/docs/Build_Metadata_Reference/).
|
||||||
99
app-k9mail/badging/fossRelease-badging.txt
Normal file
99
app-k9mail/badging/fossRelease-badging.txt
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
application-icon-120:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-160:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-240:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-320:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-480:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-640:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-65534:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-label-ar:'بريد K-9'
|
||||||
|
application-label-be:'Пошта K-9'
|
||||||
|
application-label-bg:'K-9 Поща'
|
||||||
|
application-label-ca:'K-9 Mail'
|
||||||
|
application-label-co:'K-9 Mail'
|
||||||
|
application-label-cs:'K-9 Mail'
|
||||||
|
application-label-cy:'K-9 Mail'
|
||||||
|
application-label-da:'K-9 Mail'
|
||||||
|
application-label-de:'K-9 Mail'
|
||||||
|
application-label-el:'K-9 Mail'
|
||||||
|
application-label-en-GB:'K-9 Mail'
|
||||||
|
application-label-en:'K-9 Mail'
|
||||||
|
application-label-eo:'K-9 Retpoŝtilo'
|
||||||
|
application-label-es:'K-9 Mail'
|
||||||
|
application-label-et:'K-9 Mail'
|
||||||
|
application-label-eu:'K-9 Mail'
|
||||||
|
application-label-fa:'نامهٔ کی۹'
|
||||||
|
application-label-fi:'K-9 Mail'
|
||||||
|
application-label-fr:'Courriel K-9'
|
||||||
|
application-label-fy:'K-9 Mail'
|
||||||
|
application-label-ga:'K-9 Post'
|
||||||
|
application-label-gl:'K-9 Mail'
|
||||||
|
application-label-hr:'K-9 Mail'
|
||||||
|
application-label-hu:'K-9 Mail'
|
||||||
|
application-label-in:'Surel K-9'
|
||||||
|
application-label-is:'K-9 - Póstur'
|
||||||
|
application-label-it:'K-9 Mail'
|
||||||
|
application-label-iw:'K-9 דוא\"ל'
|
||||||
|
application-label-ja:'K-9 Mail'
|
||||||
|
application-label-ko:'K-9 메일'
|
||||||
|
application-label-lt:'K-9 paštas'
|
||||||
|
application-label-lv:'K-9 pasts'
|
||||||
|
application-label-nb:'K-9 E-post'
|
||||||
|
application-label-nl:'K-9 Mail'
|
||||||
|
application-label-nn:'K-9 e-post'
|
||||||
|
application-label-pl:'K-9 Mail'
|
||||||
|
application-label-pt-BR:'K-9 Mail'
|
||||||
|
application-label-pt-PT:'K-9 Mail'
|
||||||
|
application-label-pt:'Email K-9'
|
||||||
|
application-label-ro:'K-9 Mail'
|
||||||
|
application-label-ru:'Почта K-9'
|
||||||
|
application-label-sk:'K-9 Mail'
|
||||||
|
application-label-sl:'Pošta K-9'
|
||||||
|
application-label-sq:'K-9 Mail'
|
||||||
|
application-label-sr:'K-9 Mail'
|
||||||
|
application-label-sv:'K-9 Mail'
|
||||||
|
application-label-tr:'K-9 Posta'
|
||||||
|
application-label-uk:'K-9 Mail'
|
||||||
|
application-label-vi:'Thư K-9'
|
||||||
|
application-label-zh-CN:'K-9 Mail'
|
||||||
|
application-label-zh-TW:'K-9 Mail'
|
||||||
|
application-label-zh:'K-9 Mail'
|
||||||
|
application-label:'K-9 Mail'
|
||||||
|
application: label='K-9 Mail' icon='res/drawable-v26/ic_launcher.xml'
|
||||||
|
densities: '120' '160' '240' '320' '480' '640' '65534'
|
||||||
|
feature-group: label=''
|
||||||
|
install-location:'auto'
|
||||||
|
launchable-activity: name='com.fsck.k9.activity.MessageList' label='' icon=''
|
||||||
|
locales: '--_--' 'ar' 'be' 'bg' 'ca' 'co' 'cs' 'cy' 'da' 'de' 'el' 'en' 'en-GB' 'eo' 'es' 'et' 'eu' 'fa' 'fi' 'fr' 'fy' 'ga' 'gl' 'hr' 'hu' 'in' 'is' 'it' 'iw' 'ja' 'ko' 'lt' 'lv' 'nb' 'nl' 'nn' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'sk' 'sl' 'sq' 'sr' 'sv' 'tr' 'uk' 'vi' 'zh' 'zh-CN' 'zh-TW'
|
||||||
|
main
|
||||||
|
minSdkVersion:'21'
|
||||||
|
native-code: 'arm64-v8a' 'armeabi-v7a' 'x86' 'x86_64'
|
||||||
|
other-activities
|
||||||
|
other-receivers
|
||||||
|
other-services
|
||||||
|
package: name='com.fsck.k9' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
|
||||||
|
property: name='android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE' value='This service is used to maintain a continuous connection to an IMAP server to be able to provide instant notifications to the user when a new email arrives. Firebase Cloud Messaging is not suitable for this task, neither are mechanisms like AndroidX WorkManager. Other foreground service types aren't a good fit for this use case.'
|
||||||
|
provides-component:'app-widget'
|
||||||
|
supports-any-density: 'true'
|
||||||
|
supports-screens: 'small' 'normal' 'large' 'xlarge'
|
||||||
|
targetSdkVersion:'35'
|
||||||
|
uses-feature-not-required: name='android.hardware.camera'
|
||||||
|
uses-feature-not-required: name='android.hardware.touchscreen'
|
||||||
|
uses-library-not-required:'androidx.window.extensions'
|
||||||
|
uses-library-not-required:'androidx.window.sidecar'
|
||||||
|
uses-library-not-required:'com.sec.android.app.multiwindow'
|
||||||
|
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
|
||||||
|
uses-permission: name='android.permission.CAMERA'
|
||||||
|
uses-permission: name='android.permission.FOREGROUND_SERVICE'
|
||||||
|
uses-permission: name='android.permission.FOREGROUND_SERVICE_DATA_SYNC' maxSdkVersion='33'
|
||||||
|
uses-permission: name='android.permission.FOREGROUND_SERVICE_SPECIAL_USE'
|
||||||
|
uses-permission: name='android.permission.INTERNET'
|
||||||
|
uses-permission: name='android.permission.POST_NOTIFICATIONS'
|
||||||
|
uses-permission: name='android.permission.READ_CONTACTS'
|
||||||
|
uses-permission: name='android.permission.READ_SYNC_SETTINGS'
|
||||||
|
uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED'
|
||||||
|
uses-permission: name='android.permission.SCHEDULE_EXACT_ALARM'
|
||||||
|
uses-permission: name='android.permission.USE_BIOMETRIC'
|
||||||
|
uses-permission: name='android.permission.USE_FINGERPRINT'
|
||||||
|
uses-permission: name='android.permission.VIBRATE'
|
||||||
|
uses-permission: name='android.permission.WAKE_LOCK'
|
||||||
|
uses-permission: name='com.fsck.k9.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION'
|
||||||
100
app-k9mail/badging/fullRelease-badging.txt
Normal file
100
app-k9mail/badging/fullRelease-badging.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
application-icon-120:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-160:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-240:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-320:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-480:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-640:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-icon-65534:'res/drawable-v26/ic_launcher.xml'
|
||||||
|
application-label-ar:'بريد K-9'
|
||||||
|
application-label-be:'Пошта K-9'
|
||||||
|
application-label-bg:'K-9 Поща'
|
||||||
|
application-label-ca:'K-9 Mail'
|
||||||
|
application-label-co:'K-9 Mail'
|
||||||
|
application-label-cs:'K-9 Mail'
|
||||||
|
application-label-cy:'K-9 Mail'
|
||||||
|
application-label-da:'K-9 Mail'
|
||||||
|
application-label-de:'K-9 Mail'
|
||||||
|
application-label-el:'K-9 Mail'
|
||||||
|
application-label-en-GB:'K-9 Mail'
|
||||||
|
application-label-en:'K-9 Mail'
|
||||||
|
application-label-eo:'K-9 Retpoŝtilo'
|
||||||
|
application-label-es:'K-9 Mail'
|
||||||
|
application-label-et:'K-9 Mail'
|
||||||
|
application-label-eu:'K-9 Mail'
|
||||||
|
application-label-fa:'نامهٔ کی۹'
|
||||||
|
application-label-fi:'K-9 Mail'
|
||||||
|
application-label-fr:'Courriel K-9'
|
||||||
|
application-label-fy:'K-9 Mail'
|
||||||
|
application-label-ga:'K-9 Post'
|
||||||
|
application-label-gl:'K-9 Mail'
|
||||||
|
application-label-hr:'K-9 Mail'
|
||||||
|
application-label-hu:'K-9 Mail'
|
||||||
|
application-label-in:'Surel K-9'
|
||||||
|
application-label-is:'K-9 - Póstur'
|
||||||
|
application-label-it:'K-9 Mail'
|
||||||
|
application-label-iw:'K-9 דוא\"ל'
|
||||||
|
application-label-ja:'K-9 Mail'
|
||||||
|
application-label-ko:'K-9 메일'
|
||||||
|
application-label-lt:'K-9 paštas'
|
||||||
|
application-label-lv:'K-9 pasts'
|
||||||
|
application-label-nb:'K-9 E-post'
|
||||||
|
application-label-nl:'K-9 Mail'
|
||||||
|
application-label-nn:'K-9 e-post'
|
||||||
|
application-label-pl:'K-9 Mail'
|
||||||
|
application-label-pt-BR:'K-9 Mail'
|
||||||
|
application-label-pt-PT:'K-9 Mail'
|
||||||
|
application-label-pt:'Email K-9'
|
||||||
|
application-label-ro:'K-9 Mail'
|
||||||
|
application-label-ru:'Почта K-9'
|
||||||
|
application-label-sk:'K-9 Mail'
|
||||||
|
application-label-sl:'Pošta K-9'
|
||||||
|
application-label-sq:'K-9 Mail'
|
||||||
|
application-label-sr:'K-9 Mail'
|
||||||
|
application-label-sv:'K-9 Mail'
|
||||||
|
application-label-tr:'K-9 Posta'
|
||||||
|
application-label-uk:'K-9 Mail'
|
||||||
|
application-label-vi:'Thư K-9'
|
||||||
|
application-label-zh-CN:'K-9 Mail'
|
||||||
|
application-label-zh-TW:'K-9 Mail'
|
||||||
|
application-label-zh:'K-9 Mail'
|
||||||
|
application-label:'K-9 Mail'
|
||||||
|
application: label='K-9 Mail' icon='res/drawable-v26/ic_launcher.xml'
|
||||||
|
densities: '120' '160' '240' '320' '480' '640' '65534'
|
||||||
|
feature-group: label=''
|
||||||
|
install-location:'auto'
|
||||||
|
launchable-activity: name='com.fsck.k9.activity.MessageList' label='' icon=''
|
||||||
|
locales: '--_--' 'ar' 'be' 'bg' 'ca' 'co' 'cs' 'cy' 'da' 'de' 'el' 'en' 'en-GB' 'eo' 'es' 'et' 'eu' 'fa' 'fi' 'fr' 'fy' 'ga' 'gl' 'hr' 'hu' 'in' 'is' 'it' 'iw' 'ja' 'ko' 'lt' 'lv' 'nb' 'nl' 'nn' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'sk' 'sl' 'sq' 'sr' 'sv' 'tr' 'uk' 'vi' 'zh' 'zh-CN' 'zh-TW'
|
||||||
|
main
|
||||||
|
minSdkVersion:'21'
|
||||||
|
native-code: 'arm64-v8a' 'armeabi-v7a' 'x86' 'x86_64'
|
||||||
|
other-activities
|
||||||
|
other-receivers
|
||||||
|
other-services
|
||||||
|
package: name='com.fsck.k9' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
|
||||||
|
property: name='android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE' value='This service is used to maintain a continuous connection to an IMAP server to be able to provide instant notifications to the user when a new email arrives. Firebase Cloud Messaging is not suitable for this task, neither are mechanisms like AndroidX WorkManager. Other foreground service types aren't a good fit for this use case.'
|
||||||
|
provides-component:'app-widget'
|
||||||
|
supports-any-density: 'true'
|
||||||
|
supports-screens: 'small' 'normal' 'large' 'xlarge'
|
||||||
|
targetSdkVersion:'35'
|
||||||
|
uses-feature-not-required: name='android.hardware.camera'
|
||||||
|
uses-feature-not-required: name='android.hardware.touchscreen'
|
||||||
|
uses-library-not-required:'androidx.window.extensions'
|
||||||
|
uses-library-not-required:'androidx.window.sidecar'
|
||||||
|
uses-library-not-required:'com.sec.android.app.multiwindow'
|
||||||
|
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
|
||||||
|
uses-permission: name='android.permission.CAMERA'
|
||||||
|
uses-permission: name='android.permission.FOREGROUND_SERVICE'
|
||||||
|
uses-permission: name='android.permission.FOREGROUND_SERVICE_DATA_SYNC' maxSdkVersion='33'
|
||||||
|
uses-permission: name='android.permission.FOREGROUND_SERVICE_SPECIAL_USE'
|
||||||
|
uses-permission: name='android.permission.INTERNET'
|
||||||
|
uses-permission: name='android.permission.POST_NOTIFICATIONS'
|
||||||
|
uses-permission: name='android.permission.READ_CONTACTS'
|
||||||
|
uses-permission: name='android.permission.READ_SYNC_SETTINGS'
|
||||||
|
uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED'
|
||||||
|
uses-permission: name='android.permission.SCHEDULE_EXACT_ALARM'
|
||||||
|
uses-permission: name='android.permission.USE_BIOMETRIC'
|
||||||
|
uses-permission: name='android.permission.USE_FINGERPRINT'
|
||||||
|
uses-permission: name='android.permission.VIBRATE'
|
||||||
|
uses-permission: name='android.permission.WAKE_LOCK'
|
||||||
|
uses-permission: name='com.android.vending.BILLING'
|
||||||
|
uses-permission: name='com.fsck.k9.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION'
|
||||||
175
app-k9mail/build.gradle.kts
Normal file
175
app-k9mail/build.gradle.kts
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
plugins {
|
||||||
|
id(ThunderbirdPlugins.App.androidCompose)
|
||||||
|
alias(libs.plugins.dependency.guard)
|
||||||
|
id("thunderbird.app.version.info")
|
||||||
|
id("thunderbird.quality.badging")
|
||||||
|
}
|
||||||
|
|
||||||
|
val testCoverageEnabled: Boolean by extra
|
||||||
|
if (testCoverageEnabled) {
|
||||||
|
apply(plugin = "jacoco")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.fsck.k9"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.fsck.k9"
|
||||||
|
testApplicationId = "com.fsck.k9.tests"
|
||||||
|
|
||||||
|
versionCode = 39029
|
||||||
|
versionName = "13.0"
|
||||||
|
|
||||||
|
buildConfigField("String", "CLIENT_INFO_APP_NAME", "\"K-9 Mail\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
androidResources {
|
||||||
|
// Keep in sync with the resource string array "supported_languages"
|
||||||
|
localeFilters += listOf(
|
||||||
|
"ar",
|
||||||
|
"be",
|
||||||
|
"bg",
|
||||||
|
"ca",
|
||||||
|
"co",
|
||||||
|
"cs",
|
||||||
|
"cy",
|
||||||
|
"da",
|
||||||
|
"de",
|
||||||
|
"el",
|
||||||
|
"en",
|
||||||
|
"en-rGB",
|
||||||
|
"eo",
|
||||||
|
"es",
|
||||||
|
"et",
|
||||||
|
"eu",
|
||||||
|
"fa",
|
||||||
|
"fi",
|
||||||
|
"fr",
|
||||||
|
"fy",
|
||||||
|
"ga",
|
||||||
|
"gl",
|
||||||
|
"hr",
|
||||||
|
"hu",
|
||||||
|
"in",
|
||||||
|
"is",
|
||||||
|
"it",
|
||||||
|
"iw",
|
||||||
|
"ja",
|
||||||
|
"ko",
|
||||||
|
"lt",
|
||||||
|
"lv",
|
||||||
|
"nb",
|
||||||
|
"nl",
|
||||||
|
"nn",
|
||||||
|
"pl",
|
||||||
|
"pt-rBR",
|
||||||
|
"pt-rPT",
|
||||||
|
"ro",
|
||||||
|
"ru",
|
||||||
|
"sk",
|
||||||
|
"sl",
|
||||||
|
"sq",
|
||||||
|
"sr",
|
||||||
|
"sv",
|
||||||
|
"tr",
|
||||||
|
"uk",
|
||||||
|
"vi",
|
||||||
|
"zh-rCN",
|
||||||
|
"zh-rTW",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
createSigningConfig(project, SigningType.K9_RELEASE, isUpload = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig = signingConfigs.getByType(SigningType.K9_RELEASE)
|
||||||
|
|
||||||
|
isMinifyEnabled = true
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android.txt"),
|
||||||
|
"proguard-rules.pro",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug {
|
||||||
|
applicationIdSuffix = ".debug"
|
||||||
|
enableUnitTestCoverage = testCoverageEnabled
|
||||||
|
enableAndroidTestCoverage = testCoverageEnabled
|
||||||
|
|
||||||
|
isMinifyEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flavorDimensions += listOf("app")
|
||||||
|
productFlavors {
|
||||||
|
create("foss") {
|
||||||
|
dimension = "app"
|
||||||
|
buildConfigField("String", "PRODUCT_FLAVOR_APP", "\"foss\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
create("full") {
|
||||||
|
dimension = "app"
|
||||||
|
buildConfigField("String", "PRODUCT_FLAVOR_APP", "\"full\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
jniLibs {
|
||||||
|
excludes += listOf("kotlin/**")
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
excludes += listOf(
|
||||||
|
"META-INF/*.kotlin_module",
|
||||||
|
"META-INF/*.version",
|
||||||
|
"kotlin/**",
|
||||||
|
"DebugProbesKt.bin",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.appCommon)
|
||||||
|
implementation(projects.core.ui.compose.theme2.k9mail)
|
||||||
|
implementation(projects.core.ui.legacy.theme2.k9mail)
|
||||||
|
implementation(projects.feature.launcher)
|
||||||
|
implementation(projects.feature.mail.message.list)
|
||||||
|
|
||||||
|
implementation(projects.legacy.core)
|
||||||
|
implementation(projects.legacy.ui.legacy)
|
||||||
|
|
||||||
|
implementation(projects.core.featureflag)
|
||||||
|
|
||||||
|
implementation(projects.feature.account.settings.impl)
|
||||||
|
|
||||||
|
"fossImplementation"(projects.feature.funding.noop)
|
||||||
|
"fullImplementation"(projects.feature.funding.googleplay)
|
||||||
|
implementation(projects.feature.migration.launcher.noop)
|
||||||
|
implementation(projects.feature.onboarding.migration.noop)
|
||||||
|
implementation(projects.feature.telemetry.noop)
|
||||||
|
implementation(projects.feature.widget.messageList)
|
||||||
|
implementation(projects.feature.widget.messageListGlance)
|
||||||
|
implementation(projects.feature.widget.shortcut)
|
||||||
|
implementation(projects.feature.widget.unread)
|
||||||
|
|
||||||
|
implementation(libs.androidx.work.runtime)
|
||||||
|
|
||||||
|
implementation(projects.feature.autodiscovery.api)
|
||||||
|
debugImplementation(projects.backend.demo)
|
||||||
|
debugImplementation(projects.feature.autodiscovery.demo)
|
||||||
|
|
||||||
|
// Required for DependencyInjectionTest
|
||||||
|
testImplementation(projects.feature.account.api)
|
||||||
|
testImplementation(projects.feature.account.common)
|
||||||
|
testImplementation(projects.plugins.openpgpApiLib.openpgpApi)
|
||||||
|
testImplementation(libs.appauth)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyGuard {
|
||||||
|
configuration("fossReleaseRuntimeClasspath")
|
||||||
|
configuration("fullReleaseRuntimeClasspath")
|
||||||
|
}
|
||||||
286
app-k9mail/dependencies/fossReleaseRuntimeClasspath.txt
Normal file
286
app-k9mail/dependencies/fossReleaseRuntimeClasspath.txt
Normal file
|
|
@ -0,0 +1,286 @@
|
||||||
|
androidx.activity:activity-compose:1.10.1
|
||||||
|
androidx.activity:activity-ktx:1.10.1
|
||||||
|
androidx.activity:activity:1.10.1
|
||||||
|
androidx.annotation:annotation-experimental:1.4.1
|
||||||
|
androidx.annotation:annotation-jvm:1.9.1
|
||||||
|
androidx.annotation:annotation:1.9.1
|
||||||
|
androidx.appcompat:appcompat-resources:1.7.1
|
||||||
|
androidx.appcompat:appcompat:1.7.1
|
||||||
|
androidx.arch.core:core-common:2.2.0
|
||||||
|
androidx.arch.core:core-runtime:2.2.0
|
||||||
|
androidx.autofill:autofill:1.3.0
|
||||||
|
androidx.biometric:biometric:1.1.0
|
||||||
|
androidx.browser:browser:1.3.0
|
||||||
|
androidx.cardview:cardview:1.0.0
|
||||||
|
androidx.collection:collection-jvm:1.5.0
|
||||||
|
androidx.collection:collection-ktx:1.5.0
|
||||||
|
androidx.collection:collection:1.5.0
|
||||||
|
androidx.compose.animation:animation-android:1.8.3
|
||||||
|
androidx.compose.animation:animation-core-android:1.8.3
|
||||||
|
androidx.compose.animation:animation-core:1.8.3
|
||||||
|
androidx.compose.animation:animation:1.8.3
|
||||||
|
androidx.compose.foundation:foundation-android:1.8.3
|
||||||
|
androidx.compose.foundation:foundation-layout-android:1.8.3
|
||||||
|
androidx.compose.foundation:foundation-layout:1.8.3
|
||||||
|
androidx.compose.foundation:foundation:1.8.3
|
||||||
|
androidx.compose.material3.adaptive:adaptive-android:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive-layout-android:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive-layout:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive-navigation-android:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive-navigation:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive:1.1.0
|
||||||
|
androidx.compose.material3:material3-android:1.3.2
|
||||||
|
androidx.compose.material3:material3:1.3.2
|
||||||
|
androidx.compose.material:material-icons-core-android:1.7.8
|
||||||
|
androidx.compose.material:material-icons-core:1.7.8
|
||||||
|
androidx.compose.material:material-icons-extended-android:1.7.8
|
||||||
|
androidx.compose.material:material-icons-extended:1.7.8
|
||||||
|
androidx.compose.material:material-ripple-android:1.8.3
|
||||||
|
androidx.compose.material:material-ripple:1.8.3
|
||||||
|
androidx.compose.runtime:runtime-android:1.8.3
|
||||||
|
androidx.compose.runtime:runtime-saveable-android:1.8.3
|
||||||
|
androidx.compose.runtime:runtime-saveable:1.8.3
|
||||||
|
androidx.compose.runtime:runtime:1.8.3
|
||||||
|
androidx.compose.ui:ui-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-geometry-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-geometry:1.8.3
|
||||||
|
androidx.compose.ui:ui-graphics-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-graphics:1.8.3
|
||||||
|
androidx.compose.ui:ui-text-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-text:1.8.3
|
||||||
|
androidx.compose.ui:ui-tooling-preview-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-tooling-preview:1.8.3
|
||||||
|
androidx.compose.ui:ui-unit-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-unit:1.8.3
|
||||||
|
androidx.compose.ui:ui-util-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-util:1.8.3
|
||||||
|
androidx.compose.ui:ui:1.8.3
|
||||||
|
androidx.compose:compose-bom:2025.07.00
|
||||||
|
androidx.concurrent:concurrent-futures-ktx:1.1.0
|
||||||
|
androidx.concurrent:concurrent-futures:1.1.0
|
||||||
|
androidx.constraintlayout:constraintlayout-core:1.1.1
|
||||||
|
androidx.constraintlayout:constraintlayout:2.2.1
|
||||||
|
androidx.coordinatorlayout:coordinatorlayout:1.3.0
|
||||||
|
androidx.core:core-ktx:1.16.0
|
||||||
|
androidx.core:core-remoteviews:1.1.0
|
||||||
|
androidx.core:core-splashscreen:1.0.1
|
||||||
|
androidx.core:core-viewtree:1.0.0
|
||||||
|
androidx.core:core:1.16.0
|
||||||
|
androidx.cursoradapter:cursoradapter:1.0.0
|
||||||
|
androidx.customview:customview-poolingcontainer:1.0.0
|
||||||
|
androidx.customview:customview:1.1.0
|
||||||
|
androidx.datastore:datastore-core:1.0.0
|
||||||
|
androidx.datastore:datastore-preferences-core:1.0.0
|
||||||
|
androidx.datastore:datastore-preferences:1.0.0
|
||||||
|
androidx.datastore:datastore:1.0.0
|
||||||
|
androidx.documentfile:documentfile:1.0.0
|
||||||
|
androidx.drawerlayout:drawerlayout:1.1.1
|
||||||
|
androidx.dynamicanimation:dynamicanimation:1.0.0
|
||||||
|
androidx.emoji2:emoji2-views-helper:1.4.0
|
||||||
|
androidx.emoji2:emoji2:1.4.0
|
||||||
|
androidx.exifinterface:exifinterface:1.4.1
|
||||||
|
androidx.fragment:fragment-compose:1.8.8
|
||||||
|
androidx.fragment:fragment-ktx:1.8.8
|
||||||
|
androidx.fragment:fragment:1.8.8
|
||||||
|
androidx.glance:glance-appwidget-external-protobuf:1.1.1
|
||||||
|
androidx.glance:glance-appwidget-proto:1.1.1
|
||||||
|
androidx.glance:glance-appwidget:1.1.1
|
||||||
|
androidx.glance:glance-material3:1.1.1
|
||||||
|
androidx.glance:glance:1.1.1
|
||||||
|
androidx.graphics:graphics-path:1.0.1
|
||||||
|
androidx.interpolator:interpolator:1.0.0
|
||||||
|
androidx.legacy:legacy-support-core-utils:1.0.0
|
||||||
|
androidx.lifecycle:lifecycle-common-java8:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-common-jvm:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-common:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-livedata-core-ktx:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-livedata-core:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-livedata-ktx:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-livedata:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-process:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-compose-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-compose:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-ktx-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-ktx:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-service:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-compose:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel:2.9.2
|
||||||
|
androidx.loader:loader:1.0.0
|
||||||
|
androidx.localbroadcastmanager:localbroadcastmanager:1.1.0
|
||||||
|
androidx.navigation:navigation-common-android:2.9.3
|
||||||
|
androidx.navigation:navigation-common:2.9.3
|
||||||
|
androidx.navigation:navigation-compose-android:2.9.3
|
||||||
|
androidx.navigation:navigation-compose:2.9.3
|
||||||
|
androidx.navigation:navigation-fragment:2.9.3
|
||||||
|
androidx.navigation:navigation-runtime-android:2.9.3
|
||||||
|
androidx.navigation:navigation-runtime:2.9.3
|
||||||
|
androidx.navigation:navigation-ui:2.9.3
|
||||||
|
androidx.preference:preference:1.2.1
|
||||||
|
androidx.print:print:1.0.0
|
||||||
|
androidx.profileinstaller:profileinstaller:1.4.1
|
||||||
|
androidx.recyclerview:recyclerview:1.4.0
|
||||||
|
androidx.resourceinspection:resourceinspection-annotation:1.0.1
|
||||||
|
androidx.room:room-common:2.6.1
|
||||||
|
androidx.room:room-ktx:2.6.1
|
||||||
|
androidx.room:room-runtime:2.6.1
|
||||||
|
androidx.savedstate:savedstate-android:1.3.1
|
||||||
|
androidx.savedstate:savedstate-compose-android:1.3.1
|
||||||
|
androidx.savedstate:savedstate-compose:1.3.1
|
||||||
|
androidx.savedstate:savedstate-ktx:1.3.1
|
||||||
|
androidx.savedstate:savedstate:1.3.1
|
||||||
|
androidx.slidingpanelayout:slidingpanelayout:1.2.0
|
||||||
|
androidx.sqlite:sqlite-framework:2.4.0
|
||||||
|
androidx.sqlite:sqlite:2.4.0
|
||||||
|
androidx.startup:startup-runtime:1.1.1
|
||||||
|
androidx.swiperefreshlayout:swiperefreshlayout:1.1.0
|
||||||
|
androidx.tracing:tracing-ktx:1.2.0
|
||||||
|
androidx.tracing:tracing:1.2.0
|
||||||
|
androidx.transition:transition:1.5.0
|
||||||
|
androidx.vectordrawable:vectordrawable-animated:1.2.0
|
||||||
|
androidx.vectordrawable:vectordrawable:1.2.0
|
||||||
|
androidx.versionedparcelable:versionedparcelable:1.1.1
|
||||||
|
androidx.viewpager2:viewpager2:1.1.0-beta02
|
||||||
|
androidx.viewpager:viewpager:1.0.0
|
||||||
|
androidx.webkit:webkit:1.14.0
|
||||||
|
androidx.window.extensions.core:core:1.0.0
|
||||||
|
androidx.window:window-core-android:1.3.0
|
||||||
|
androidx.window:window-core:1.3.0
|
||||||
|
androidx.window:window:1.3.0
|
||||||
|
androidx.work:work-runtime-ktx:2.10.3
|
||||||
|
androidx.work:work-runtime:2.10.3
|
||||||
|
co.touchlab:stately-concurrency-jvm:2.1.0
|
||||||
|
co.touchlab:stately-concurrency:2.1.0
|
||||||
|
co.touchlab:stately-concurrent-collections-jvm:2.1.0
|
||||||
|
co.touchlab:stately-concurrent-collections:2.1.0
|
||||||
|
co.touchlab:stately-strict-jvm:2.1.0
|
||||||
|
co.touchlab:stately-strict:2.1.0
|
||||||
|
com.beetstra.jutf7:jutf7:1.0.0
|
||||||
|
com.github.ByteHamster:SearchPreference:2.7.3
|
||||||
|
com.github.bumptech.glide:annotations:4.16.0
|
||||||
|
com.github.bumptech.glide:disklrucache:4.16.0
|
||||||
|
com.github.bumptech.glide:gifdecoder:4.16.0
|
||||||
|
com.github.bumptech.glide:glide:4.16.0
|
||||||
|
com.github.skydoves:landscapist-android:2.5.1
|
||||||
|
com.github.skydoves:landscapist-coil3-android:2.5.1
|
||||||
|
com.github.skydoves:landscapist-coil3:2.5.1
|
||||||
|
com.github.skydoves:landscapist:2.5.1
|
||||||
|
com.google.android.flexbox:flexbox:3.0.0
|
||||||
|
com.google.android.material:material:1.12.0
|
||||||
|
com.google.errorprone:error_prone_annotations:2.15.0
|
||||||
|
com.google.guava:listenablefuture:1.0
|
||||||
|
com.jakewharton.timber:timber:5.0.1
|
||||||
|
com.jcraft:jzlib:1.0.7
|
||||||
|
com.mikepenz:fastadapter-extensions-drag:5.7.0
|
||||||
|
com.mikepenz:fastadapter-extensions-expandable:5.7.0
|
||||||
|
com.mikepenz:fastadapter-extensions-swipe:5.7.0
|
||||||
|
com.mikepenz:fastadapter-extensions-utils:5.7.0
|
||||||
|
com.mikepenz:fastadapter:5.7.0
|
||||||
|
com.squareup.moshi:moshi:1.15.2
|
||||||
|
com.squareup.okhttp3:okhttp:4.12.0
|
||||||
|
com.squareup.okio:okio-jvm:3.16.0
|
||||||
|
com.squareup.okio:okio:3.16.0
|
||||||
|
com.takisoft.colorpicker:colorpicker:1.0.0
|
||||||
|
com.takisoft.datetimepicker:datetimepicker:1.0.2
|
||||||
|
com.takisoft.preferencex:preferencex-colorpicker:1.1.0
|
||||||
|
com.takisoft.preferencex:preferencex-datetimepicker:1.1.0
|
||||||
|
com.takisoft.preferencex:preferencex:1.1.0
|
||||||
|
commons-io:commons-io:2.20.0
|
||||||
|
de.cketti.library.changelog:ckchangelog-core:2.0.0-beta02
|
||||||
|
de.cketti.safecontentresolver:safe-content-resolver-v21:1.0.0
|
||||||
|
de.hdodenhof:circleimageview:3.1.0
|
||||||
|
io.coil-kt.coil3:coil-android:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-core-android:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-core:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-gif:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-network-core-android:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-network-core:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-network-okhttp-jvm:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-network-okhttp:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-video:3.2.0
|
||||||
|
io.coil-kt.coil3:coil:3.2.0
|
||||||
|
io.insert-koin:koin-android:4.1.0
|
||||||
|
io.insert-koin:koin-androidx-compose:4.1.0
|
||||||
|
io.insert-koin:koin-bom:4.1.0
|
||||||
|
io.insert-koin:koin-compose-android:4.1.0
|
||||||
|
io.insert-koin:koin-compose-viewmodel-android:4.1.0
|
||||||
|
io.insert-koin:koin-compose-viewmodel:4.1.0
|
||||||
|
io.insert-koin:koin-compose:4.1.0
|
||||||
|
io.insert-koin:koin-core-jvm:4.1.0
|
||||||
|
io.insert-koin:koin-core-viewmodel-android:4.1.0
|
||||||
|
io.insert-koin:koin-core-viewmodel:4.1.0
|
||||||
|
io.insert-koin:koin-core:4.1.0
|
||||||
|
net.jcip:jcip-annotations:1.0
|
||||||
|
net.openid:appauth:0.11.1
|
||||||
|
org.apache.commons:commons-lang3:3.7
|
||||||
|
org.apache.commons:commons-text:1.3
|
||||||
|
org.apache.httpcomponents.client5:httpclient5:5.5
|
||||||
|
org.apache.httpcomponents.core5:httpcore5-h2:5.3.4
|
||||||
|
org.apache.httpcomponents.core5:httpcore5:5.3.4
|
||||||
|
org.apache.james:apache-mime4j-core:0.8.13
|
||||||
|
org.apache.james:apache-mime4j-dom:0.8.13
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-common:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.9.1
|
||||||
|
org.jetbrains.androidx.savedstate:savedstate:1.3.1
|
||||||
|
org.jetbrains.compose.animation:animation-core:1.8.2
|
||||||
|
org.jetbrains.compose.animation:animation:1.8.2
|
||||||
|
org.jetbrains.compose.annotation-internal:annotation:1.8.2
|
||||||
|
org.jetbrains.compose.collection-internal:collection:1.8.2
|
||||||
|
org.jetbrains.compose.components:components-resources-android:1.8.2
|
||||||
|
org.jetbrains.compose.components:components-resources:1.8.2
|
||||||
|
org.jetbrains.compose.components:components-ui-tooling-preview-android:1.8.2
|
||||||
|
org.jetbrains.compose.components:components-ui-tooling-preview:1.8.2
|
||||||
|
org.jetbrains.compose.foundation:foundation-layout:1.8.2
|
||||||
|
org.jetbrains.compose.foundation:foundation:1.8.2
|
||||||
|
org.jetbrains.compose.runtime:runtime-saveable:1.8.2
|
||||||
|
org.jetbrains.compose.runtime:runtime:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-geometry:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-graphics:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-text:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-tooling-preview:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-unit:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-util:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui:1.8.2
|
||||||
|
org.jetbrains.kotlin:kotlin-android-extensions-runtime:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-bom:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib-common:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib:2.2.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.4.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2
|
||||||
|
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.7.1
|
||||||
|
org.jetbrains.kotlinx:kotlinx-datetime:0.7.1
|
||||||
|
org.jetbrains.kotlinx:kotlinx-io-bytestring-jvm:0.8.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-io-bytestring:0.8.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-io-core-jvm:0.8.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-io-core:0.8.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.9.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-core:1.9.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.9.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0
|
||||||
|
org.jetbrains:annotations:26.0.2
|
||||||
|
org.jsoup:jsoup:1.19.1
|
||||||
|
org.jspecify:jspecify:1.0.0
|
||||||
|
org.minidns:minidns-client:1.1.1
|
||||||
|
org.minidns:minidns-core:1.1.1
|
||||||
|
org.minidns:minidns-dnssec:1.1.1
|
||||||
|
org.minidns:minidns-hla:1.1.1
|
||||||
|
org.minidns:minidns-iterative-resolver:1.1.1
|
||||||
|
org.slf4j:slf4j-api:1.7.36
|
||||||
300
app-k9mail/dependencies/fullReleaseRuntimeClasspath.txt
Normal file
300
app-k9mail/dependencies/fullReleaseRuntimeClasspath.txt
Normal file
|
|
@ -0,0 +1,300 @@
|
||||||
|
androidx.activity:activity-compose:1.10.1
|
||||||
|
androidx.activity:activity-ktx:1.10.1
|
||||||
|
androidx.activity:activity:1.10.1
|
||||||
|
androidx.annotation:annotation-experimental:1.4.1
|
||||||
|
androidx.annotation:annotation-jvm:1.9.1
|
||||||
|
androidx.annotation:annotation:1.9.1
|
||||||
|
androidx.appcompat:appcompat-resources:1.7.1
|
||||||
|
androidx.appcompat:appcompat:1.7.1
|
||||||
|
androidx.arch.core:core-common:2.2.0
|
||||||
|
androidx.arch.core:core-runtime:2.2.0
|
||||||
|
androidx.autofill:autofill:1.3.0
|
||||||
|
androidx.biometric:biometric:1.1.0
|
||||||
|
androidx.browser:browser:1.3.0
|
||||||
|
androidx.cardview:cardview:1.0.0
|
||||||
|
androidx.collection:collection-jvm:1.5.0
|
||||||
|
androidx.collection:collection-ktx:1.5.0
|
||||||
|
androidx.collection:collection:1.5.0
|
||||||
|
androidx.compose.animation:animation-android:1.8.3
|
||||||
|
androidx.compose.animation:animation-core-android:1.8.3
|
||||||
|
androidx.compose.animation:animation-core:1.8.3
|
||||||
|
androidx.compose.animation:animation:1.8.3
|
||||||
|
androidx.compose.foundation:foundation-android:1.8.3
|
||||||
|
androidx.compose.foundation:foundation-layout-android:1.8.3
|
||||||
|
androidx.compose.foundation:foundation-layout:1.8.3
|
||||||
|
androidx.compose.foundation:foundation:1.8.3
|
||||||
|
androidx.compose.material3.adaptive:adaptive-android:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive-layout-android:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive-layout:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive-navigation-android:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive-navigation:1.1.0
|
||||||
|
androidx.compose.material3.adaptive:adaptive:1.1.0
|
||||||
|
androidx.compose.material3:material3-android:1.3.2
|
||||||
|
androidx.compose.material3:material3:1.3.2
|
||||||
|
androidx.compose.material:material-icons-core-android:1.7.8
|
||||||
|
androidx.compose.material:material-icons-core:1.7.8
|
||||||
|
androidx.compose.material:material-icons-extended-android:1.7.8
|
||||||
|
androidx.compose.material:material-icons-extended:1.7.8
|
||||||
|
androidx.compose.material:material-ripple-android:1.8.3
|
||||||
|
androidx.compose.material:material-ripple:1.8.3
|
||||||
|
androidx.compose.runtime:runtime-android:1.8.3
|
||||||
|
androidx.compose.runtime:runtime-saveable-android:1.8.3
|
||||||
|
androidx.compose.runtime:runtime-saveable:1.8.3
|
||||||
|
androidx.compose.runtime:runtime:1.8.3
|
||||||
|
androidx.compose.ui:ui-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-geometry-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-geometry:1.8.3
|
||||||
|
androidx.compose.ui:ui-graphics-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-graphics:1.8.3
|
||||||
|
androidx.compose.ui:ui-text-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-text:1.8.3
|
||||||
|
androidx.compose.ui:ui-tooling-preview-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-tooling-preview:1.8.3
|
||||||
|
androidx.compose.ui:ui-unit-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-unit:1.8.3
|
||||||
|
androidx.compose.ui:ui-util-android:1.8.3
|
||||||
|
androidx.compose.ui:ui-util:1.8.3
|
||||||
|
androidx.compose.ui:ui:1.8.3
|
||||||
|
androidx.compose:compose-bom:2025.07.00
|
||||||
|
androidx.concurrent:concurrent-futures-ktx:1.1.0
|
||||||
|
androidx.concurrent:concurrent-futures:1.1.0
|
||||||
|
androidx.constraintlayout:constraintlayout-core:1.1.1
|
||||||
|
androidx.constraintlayout:constraintlayout:2.2.1
|
||||||
|
androidx.coordinatorlayout:coordinatorlayout:1.3.0
|
||||||
|
androidx.core:core-ktx:1.16.0
|
||||||
|
androidx.core:core-remoteviews:1.1.0
|
||||||
|
androidx.core:core-splashscreen:1.0.1
|
||||||
|
androidx.core:core-viewtree:1.0.0
|
||||||
|
androidx.core:core:1.16.0
|
||||||
|
androidx.cursoradapter:cursoradapter:1.0.0
|
||||||
|
androidx.customview:customview-poolingcontainer:1.0.0
|
||||||
|
androidx.customview:customview:1.1.0
|
||||||
|
androidx.datastore:datastore-core:1.0.0
|
||||||
|
androidx.datastore:datastore-preferences-core:1.0.0
|
||||||
|
androidx.datastore:datastore-preferences:1.0.0
|
||||||
|
androidx.datastore:datastore:1.0.0
|
||||||
|
androidx.documentfile:documentfile:1.0.0
|
||||||
|
androidx.drawerlayout:drawerlayout:1.1.1
|
||||||
|
androidx.dynamicanimation:dynamicanimation:1.0.0
|
||||||
|
androidx.emoji2:emoji2-views-helper:1.4.0
|
||||||
|
androidx.emoji2:emoji2:1.4.0
|
||||||
|
androidx.exifinterface:exifinterface:1.4.1
|
||||||
|
androidx.fragment:fragment-compose:1.8.8
|
||||||
|
androidx.fragment:fragment-ktx:1.8.8
|
||||||
|
androidx.fragment:fragment:1.8.8
|
||||||
|
androidx.glance:glance-appwidget-external-protobuf:1.1.1
|
||||||
|
androidx.glance:glance-appwidget-proto:1.1.1
|
||||||
|
androidx.glance:glance-appwidget:1.1.1
|
||||||
|
androidx.glance:glance-material3:1.1.1
|
||||||
|
androidx.glance:glance:1.1.1
|
||||||
|
androidx.graphics:graphics-path:1.0.1
|
||||||
|
androidx.interpolator:interpolator:1.0.0
|
||||||
|
androidx.legacy:legacy-support-core-utils:1.0.0
|
||||||
|
androidx.lifecycle:lifecycle-common-java8:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-common-jvm:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-common:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-livedata-core-ktx:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-livedata-core:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-livedata-ktx:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-livedata:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-process:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-compose-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-compose:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-ktx-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime-ktx:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-runtime:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-service:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-compose:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.2
|
||||||
|
androidx.lifecycle:lifecycle-viewmodel:2.9.2
|
||||||
|
androidx.loader:loader:1.0.0
|
||||||
|
androidx.localbroadcastmanager:localbroadcastmanager:1.1.0
|
||||||
|
androidx.navigation:navigation-common-android:2.9.3
|
||||||
|
androidx.navigation:navigation-common:2.9.3
|
||||||
|
androidx.navigation:navigation-compose-android:2.9.3
|
||||||
|
androidx.navigation:navigation-compose:2.9.3
|
||||||
|
androidx.navigation:navigation-fragment:2.9.3
|
||||||
|
androidx.navigation:navigation-runtime-android:2.9.3
|
||||||
|
androidx.navigation:navigation-runtime:2.9.3
|
||||||
|
androidx.navigation:navigation-ui:2.9.3
|
||||||
|
androidx.preference:preference:1.2.1
|
||||||
|
androidx.print:print:1.0.0
|
||||||
|
androidx.profileinstaller:profileinstaller:1.4.1
|
||||||
|
androidx.recyclerview:recyclerview:1.4.0
|
||||||
|
androidx.resourceinspection:resourceinspection-annotation:1.0.1
|
||||||
|
androidx.room:room-common:2.6.1
|
||||||
|
androidx.room:room-ktx:2.6.1
|
||||||
|
androidx.room:room-runtime:2.6.1
|
||||||
|
androidx.savedstate:savedstate-android:1.3.1
|
||||||
|
androidx.savedstate:savedstate-compose-android:1.3.1
|
||||||
|
androidx.savedstate:savedstate-compose:1.3.1
|
||||||
|
androidx.savedstate:savedstate-ktx:1.3.1
|
||||||
|
androidx.savedstate:savedstate:1.3.1
|
||||||
|
androidx.slidingpanelayout:slidingpanelayout:1.2.0
|
||||||
|
androidx.sqlite:sqlite-framework:2.4.0
|
||||||
|
androidx.sqlite:sqlite:2.4.0
|
||||||
|
androidx.startup:startup-runtime:1.1.1
|
||||||
|
androidx.swiperefreshlayout:swiperefreshlayout:1.1.0
|
||||||
|
androidx.tracing:tracing-ktx:1.2.0
|
||||||
|
androidx.tracing:tracing:1.2.0
|
||||||
|
androidx.transition:transition:1.5.0
|
||||||
|
androidx.vectordrawable:vectordrawable-animated:1.2.0
|
||||||
|
androidx.vectordrawable:vectordrawable:1.2.0
|
||||||
|
androidx.versionedparcelable:versionedparcelable:1.1.1
|
||||||
|
androidx.viewpager2:viewpager2:1.1.0-beta02
|
||||||
|
androidx.viewpager:viewpager:1.0.0
|
||||||
|
androidx.webkit:webkit:1.14.0
|
||||||
|
androidx.window.extensions.core:core:1.0.0
|
||||||
|
androidx.window:window-core-android:1.3.0
|
||||||
|
androidx.window:window-core:1.3.0
|
||||||
|
androidx.window:window:1.3.0
|
||||||
|
androidx.work:work-runtime-ktx:2.10.3
|
||||||
|
androidx.work:work-runtime:2.10.3
|
||||||
|
co.touchlab:stately-concurrency-jvm:2.1.0
|
||||||
|
co.touchlab:stately-concurrency:2.1.0
|
||||||
|
co.touchlab:stately-concurrent-collections-jvm:2.1.0
|
||||||
|
co.touchlab:stately-concurrent-collections:2.1.0
|
||||||
|
co.touchlab:stately-strict-jvm:2.1.0
|
||||||
|
co.touchlab:stately-strict:2.1.0
|
||||||
|
com.android.billingclient:billing-ktx:7.1.1
|
||||||
|
com.android.billingclient:billing:7.1.1
|
||||||
|
com.beetstra.jutf7:jutf7:1.0.0
|
||||||
|
com.github.ByteHamster:SearchPreference:2.7.3
|
||||||
|
com.github.bumptech.glide:annotations:4.16.0
|
||||||
|
com.github.bumptech.glide:disklrucache:4.16.0
|
||||||
|
com.github.bumptech.glide:gifdecoder:4.16.0
|
||||||
|
com.github.bumptech.glide:glide:4.16.0
|
||||||
|
com.github.skydoves:landscapist-android:2.5.1
|
||||||
|
com.github.skydoves:landscapist-coil3-android:2.5.1
|
||||||
|
com.github.skydoves:landscapist-coil3:2.5.1
|
||||||
|
com.github.skydoves:landscapist:2.5.1
|
||||||
|
com.google.android.datatransport:transport-api:3.0.0
|
||||||
|
com.google.android.datatransport:transport-backend-cct:3.1.8
|
||||||
|
com.google.android.datatransport:transport-runtime:3.1.8
|
||||||
|
com.google.android.flexbox:flexbox:3.0.0
|
||||||
|
com.google.android.gms:play-services-base:18.5.0
|
||||||
|
com.google.android.gms:play-services-basement:18.4.0
|
||||||
|
com.google.android.gms:play-services-location:19.0.0
|
||||||
|
com.google.android.gms:play-services-places-placereport:17.0.0
|
||||||
|
com.google.android.gms:play-services-tasks:18.2.0
|
||||||
|
com.google.android.material:material:1.12.0
|
||||||
|
com.google.errorprone:error_prone_annotations:2.15.0
|
||||||
|
com.google.firebase:firebase-encoders-json:18.0.0
|
||||||
|
com.google.firebase:firebase-encoders-proto:16.0.0
|
||||||
|
com.google.firebase:firebase-encoders:17.0.0
|
||||||
|
com.google.guava:listenablefuture:1.0
|
||||||
|
com.jakewharton.timber:timber:5.0.1
|
||||||
|
com.jcraft:jzlib:1.0.7
|
||||||
|
com.mikepenz:fastadapter-extensions-drag:5.7.0
|
||||||
|
com.mikepenz:fastadapter-extensions-expandable:5.7.0
|
||||||
|
com.mikepenz:fastadapter-extensions-swipe:5.7.0
|
||||||
|
com.mikepenz:fastadapter-extensions-utils:5.7.0
|
||||||
|
com.mikepenz:fastadapter:5.7.0
|
||||||
|
com.squareup.moshi:moshi:1.15.2
|
||||||
|
com.squareup.okhttp3:okhttp:4.12.0
|
||||||
|
com.squareup.okio:okio-jvm:3.16.0
|
||||||
|
com.squareup.okio:okio:3.16.0
|
||||||
|
com.takisoft.colorpicker:colorpicker:1.0.0
|
||||||
|
com.takisoft.datetimepicker:datetimepicker:1.0.2
|
||||||
|
com.takisoft.preferencex:preferencex-colorpicker:1.1.0
|
||||||
|
com.takisoft.preferencex:preferencex-datetimepicker:1.1.0
|
||||||
|
com.takisoft.preferencex:preferencex:1.1.0
|
||||||
|
commons-io:commons-io:2.20.0
|
||||||
|
de.cketti.library.changelog:ckchangelog-core:2.0.0-beta02
|
||||||
|
de.cketti.safecontentresolver:safe-content-resolver-v21:1.0.0
|
||||||
|
de.hdodenhof:circleimageview:3.1.0
|
||||||
|
io.coil-kt.coil3:coil-android:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-core-android:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-core:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-gif:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-network-core-android:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-network-core:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-network-okhttp-jvm:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-network-okhttp:3.2.0
|
||||||
|
io.coil-kt.coil3:coil-video:3.2.0
|
||||||
|
io.coil-kt.coil3:coil:3.2.0
|
||||||
|
io.insert-koin:koin-android:4.1.0
|
||||||
|
io.insert-koin:koin-androidx-compose:4.1.0
|
||||||
|
io.insert-koin:koin-bom:4.1.0
|
||||||
|
io.insert-koin:koin-compose-android:4.1.0
|
||||||
|
io.insert-koin:koin-compose-viewmodel-android:4.1.0
|
||||||
|
io.insert-koin:koin-compose-viewmodel:4.1.0
|
||||||
|
io.insert-koin:koin-compose:4.1.0
|
||||||
|
io.insert-koin:koin-core-jvm:4.1.0
|
||||||
|
io.insert-koin:koin-core-viewmodel-android:4.1.0
|
||||||
|
io.insert-koin:koin-core-viewmodel:4.1.0
|
||||||
|
io.insert-koin:koin-core:4.1.0
|
||||||
|
javax.inject:javax.inject:1
|
||||||
|
net.jcip:jcip-annotations:1.0
|
||||||
|
net.openid:appauth:0.11.1
|
||||||
|
org.apache.commons:commons-lang3:3.7
|
||||||
|
org.apache.commons:commons-text:1.3
|
||||||
|
org.apache.httpcomponents.client5:httpclient5:5.5
|
||||||
|
org.apache.httpcomponents.core5:httpcore5-h2:5.3.4
|
||||||
|
org.apache.httpcomponents.core5:httpcore5:5.3.4
|
||||||
|
org.apache.james:apache-mime4j-core:0.8.13
|
||||||
|
org.apache.james:apache-mime4j-dom:0.8.13
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-common:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.1
|
||||||
|
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.9.1
|
||||||
|
org.jetbrains.androidx.savedstate:savedstate:1.3.1
|
||||||
|
org.jetbrains.compose.animation:animation-core:1.8.2
|
||||||
|
org.jetbrains.compose.animation:animation:1.8.2
|
||||||
|
org.jetbrains.compose.annotation-internal:annotation:1.8.2
|
||||||
|
org.jetbrains.compose.collection-internal:collection:1.8.2
|
||||||
|
org.jetbrains.compose.components:components-resources-android:1.8.2
|
||||||
|
org.jetbrains.compose.components:components-resources:1.8.2
|
||||||
|
org.jetbrains.compose.components:components-ui-tooling-preview-android:1.8.2
|
||||||
|
org.jetbrains.compose.components:components-ui-tooling-preview:1.8.2
|
||||||
|
org.jetbrains.compose.foundation:foundation-layout:1.8.2
|
||||||
|
org.jetbrains.compose.foundation:foundation:1.8.2
|
||||||
|
org.jetbrains.compose.runtime:runtime-saveable:1.8.2
|
||||||
|
org.jetbrains.compose.runtime:runtime:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-geometry:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-graphics:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-text:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-tooling-preview:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-unit:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui-util:1.8.2
|
||||||
|
org.jetbrains.compose.ui:ui:1.8.2
|
||||||
|
org.jetbrains.kotlin:kotlin-android-extensions-runtime:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-bom:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib-common:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.0
|
||||||
|
org.jetbrains.kotlin:kotlin-stdlib:2.2.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.4.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2
|
||||||
|
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.7.1
|
||||||
|
org.jetbrains.kotlinx:kotlinx-datetime:0.7.1
|
||||||
|
org.jetbrains.kotlinx:kotlinx-io-bytestring-jvm:0.8.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-io-bytestring:0.8.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-io-core-jvm:0.8.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-io-core:0.8.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.9.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-core:1.9.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.9.0
|
||||||
|
org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0
|
||||||
|
org.jetbrains:annotations:26.0.2
|
||||||
|
org.jsoup:jsoup:1.19.1
|
||||||
|
org.jspecify:jspecify:1.0.0
|
||||||
|
org.minidns:minidns-client:1.1.1
|
||||||
|
org.minidns:minidns-core:1.1.1
|
||||||
|
org.minidns:minidns-dnssec:1.1.1
|
||||||
|
org.minidns:minidns-hla:1.1.1
|
||||||
|
org.minidns:minidns-iterative-resolver:1.1.1
|
||||||
|
org.slf4j:slf4j-api:1.7.36
|
||||||
64
app-k9mail/proguard-rules.pro
vendored
Normal file
64
app-k9mail/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
|
||||||
|
-dontobfuscate
|
||||||
|
|
||||||
|
# Preserve the line number information for debugging stack traces.
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# Library specific rules
|
||||||
|
-dontnote android.net.http.*
|
||||||
|
-dontnote org.apache.commons.codec.**
|
||||||
|
-dontnote org.apache.http.**
|
||||||
|
-dontnote com.squareup.moshi.**
|
||||||
|
-dontnote com.github.amlcurran.showcaseview.**
|
||||||
|
-dontnote de.cketti.safecontentresolver.**
|
||||||
|
-dontnote com.tokenautocomplete.**
|
||||||
|
|
||||||
|
-dontwarn okio.**
|
||||||
|
-dontwarn com.squareup.moshi.**
|
||||||
|
|
||||||
|
# Glide
|
||||||
|
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||||
|
-keep public class * extends com.bumptech.glide.module.LibraryGlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Project specific rules
|
||||||
|
-dontnote com.fsck.k9.ui.messageview.**
|
||||||
|
-dontnote com.fsck.k9.view.**
|
||||||
|
|
||||||
|
-assumevalues class * extends android.view.View {
|
||||||
|
boolean isInEditMode() return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep public class org.openintents.openpgp.**
|
||||||
|
|
||||||
|
-keepclassmembers class * extends androidx.appcompat.widget.SearchView {
|
||||||
|
public <init>(android.content.Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep class com.fsck.k9.mail.oauth.XOAuth2Response { *; }
|
||||||
|
|
||||||
|
# okhttp rules
|
||||||
|
# see: https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
|
||||||
|
|
||||||
|
# JSR 305 annotations are for embedding nullability information.
|
||||||
|
-dontwarn javax.annotation.**
|
||||||
|
|
||||||
|
# A resource is loaded with a relative path so the package of this class must be preserved.
|
||||||
|
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
|
||||||
|
|
||||||
|
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
|
||||||
|
-dontwarn org.codehaus.mojo.animal_sniffer.*
|
||||||
|
|
||||||
|
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
|
||||||
|
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
||||||
|
|
||||||
|
-dontwarn kotlinx.serialization.KSerializer
|
||||||
|
-dontwarn kotlinx.serialization.Serializable
|
||||||
|
-dontwarn org.apache.http.client.methods.CloseableHttpResponse
|
||||||
|
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||||
|
|
||||||
|
-keep,allowshrinking class com.tokenautocomplete.TokenCompleteTextView
|
||||||
27
app-k9mail/src/debug/AndroidManifest.xml
Normal file
27
app-k9mail/src/debug/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
>
|
||||||
|
|
||||||
|
<application tools:ignore="MissingApplicationIcon">
|
||||||
|
|
||||||
|
<!-- This component is disabled by default (if possible). It will be enabled programmatically if necessary. -->
|
||||||
|
<!-- IMPORTANT: The component name must be -->
|
||||||
|
<!-- `net.thunderbird.feature.widget.message.list.MessageListWidgetReceiver` and can't be changed. -->
|
||||||
|
<receiver
|
||||||
|
android:name="net.thunderbird.feature.widget.message.list.MessageListWidgetReceiver"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/message_list_glance_widget_label"
|
||||||
|
>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/message_list_glance_widget_info"
|
||||||
|
/>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package app.k9mail.auth
|
||||||
|
|
||||||
|
import com.fsck.k9.BuildConfig
|
||||||
|
import net.thunderbird.core.common.oauth.OAuthConfiguration
|
||||||
|
import net.thunderbird.core.common.oauth.OAuthConfigurationFactory
|
||||||
|
|
||||||
|
@Suppress("ktlint:standard:max-line-length")
|
||||||
|
class K9OAuthConfigurationFactory : OAuthConfigurationFactory {
|
||||||
|
override fun createConfigurations(): Map<List<String>, OAuthConfiguration> {
|
||||||
|
return mapOf(
|
||||||
|
createAolConfiguration(),
|
||||||
|
createFastmailConfiguration(),
|
||||||
|
createGmailConfiguration(),
|
||||||
|
createMicrosoftConfiguration(),
|
||||||
|
createYahooConfiguration(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAolConfiguration(): Pair<List<String>, OAuthConfiguration> {
|
||||||
|
return listOf(
|
||||||
|
"imap.aol.com",
|
||||||
|
"smtp.aol.com",
|
||||||
|
) to OAuthConfiguration(
|
||||||
|
clientId = "dj0yJmk9cHYydkJkTUxHcXlYJmQ9WVdrOWVHZHhVVXN4VVV3bWNHbzlNQT09JnM9Y29uc3VtZXJzZWNyZXQmc3Y9MCZ4PTdm",
|
||||||
|
scopes = listOf("mail-w"),
|
||||||
|
authorizationEndpoint = "https://api.login.aol.com/oauth2/request_auth",
|
||||||
|
tokenEndpoint = "https://api.login.aol.com/oauth2/get_token",
|
||||||
|
redirectUri = "${BuildConfig.APPLICATION_ID}://oauth2redirect",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFastmailConfiguration(): Pair<List<String>, OAuthConfiguration> {
|
||||||
|
return listOf(
|
||||||
|
"imap.fastmail.com",
|
||||||
|
"smtp.fastmail.com",
|
||||||
|
) to OAuthConfiguration(
|
||||||
|
clientId = "353641ae",
|
||||||
|
scopes = listOf("https://www.fastmail.com/dev/protocol-imap", "https://www.fastmail.com/dev/protocol-smtp"),
|
||||||
|
authorizationEndpoint = "https://api.fastmail.com/oauth/authorize",
|
||||||
|
tokenEndpoint = "https://api.fastmail.com/oauth/refresh",
|
||||||
|
redirectUri = "${BuildConfig.APPLICATION_ID}://oauth2redirect",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createGmailConfiguration(): Pair<List<String>, OAuthConfiguration> {
|
||||||
|
return listOf(
|
||||||
|
"imap.gmail.com",
|
||||||
|
"imap.googlemail.com",
|
||||||
|
"smtp.gmail.com",
|
||||||
|
"smtp.googlemail.com",
|
||||||
|
) to OAuthConfiguration(
|
||||||
|
clientId = "262622259280-5qb3vtj68d5dtudmaif4g9vd3cpar8r3.apps.googleusercontent.com",
|
||||||
|
scopes = listOf("https://mail.google.com/"),
|
||||||
|
authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth",
|
||||||
|
tokenEndpoint = "https://oauth2.googleapis.com/token",
|
||||||
|
redirectUri = "${BuildConfig.APPLICATION_ID}:/oauth2redirect",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMicrosoftConfiguration(): Pair<List<String>, OAuthConfiguration> {
|
||||||
|
return listOf(
|
||||||
|
"outlook.office365.com",
|
||||||
|
"smtp.office365.com",
|
||||||
|
"smtp-mail.outlook.com",
|
||||||
|
) to OAuthConfiguration(
|
||||||
|
clientId = "e647013a-ada4-4114-b419-e43d250f99c5",
|
||||||
|
scopes = listOf(
|
||||||
|
"openid",
|
||||||
|
"email",
|
||||||
|
"https://outlook.office.com/IMAP.AccessAsUser.All",
|
||||||
|
"https://outlook.office.com/SMTP.Send",
|
||||||
|
"offline_access",
|
||||||
|
),
|
||||||
|
authorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||||
|
tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||||
|
redirectUri = "msauth://com.fsck.k9.debug/VZF2DYuLYAu4TurFd6usQB2JPts%3D",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createYahooConfiguration(): Pair<List<String>, OAuthConfiguration> {
|
||||||
|
return listOf(
|
||||||
|
"imap.mail.yahoo.com",
|
||||||
|
"smtp.mail.yahoo.com",
|
||||||
|
) to OAuthConfiguration(
|
||||||
|
clientId = "dj0yJmk9ejRCRU1ybmZjQlVBJmQ9WVdrOVVrZEViak4xYmxZbWNHbzlNQT09JnM9Y29uc3VtZXJzZWNyZXQmc3Y9MCZ4PTZj",
|
||||||
|
scopes = listOf("mail-w"),
|
||||||
|
authorizationEndpoint = "https://api.login.yahoo.com/oauth2/request_auth",
|
||||||
|
tokenEndpoint = "https://api.login.yahoo.com/oauth2/get_token",
|
||||||
|
redirectUri = "${BuildConfig.APPLICATION_ID}://oauth2redirect",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app-k9mail/src/debug/kotlin/app/k9mail/dev/DebugConfig.kt
Normal file
17
app-k9mail/src/debug/kotlin/app/k9mail/dev/DebugConfig.kt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package app.k9mail.dev
|
||||||
|
|
||||||
|
import app.k9mail.autodiscovery.api.AutoDiscovery
|
||||||
|
import app.k9mail.autodiscovery.demo.DemoAutoDiscovery
|
||||||
|
import com.fsck.k9.backend.BackendFactory
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
|
||||||
|
fun Module.developmentModuleAdditions() {
|
||||||
|
single { DemoBackendFactory(backendStorageFactory = get()) }
|
||||||
|
single<Map<String, BackendFactory>>(named("developmentBackends")) {
|
||||||
|
mapOf("demo" to get<DemoBackendFactory>())
|
||||||
|
}
|
||||||
|
single<List<AutoDiscovery>>(named("extraAutoDiscoveries")) {
|
||||||
|
listOf(DemoAutoDiscovery())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package app.k9mail.dev
|
||||||
|
|
||||||
|
import app.k9mail.backend.demo.DemoBackend
|
||||||
|
import com.fsck.k9.backend.BackendFactory
|
||||||
|
import com.fsck.k9.backend.api.Backend
|
||||||
|
import com.fsck.k9.mailstore.K9BackendStorageFactory
|
||||||
|
import net.thunderbird.core.android.account.LegacyAccount
|
||||||
|
|
||||||
|
class DemoBackendFactory(private val backendStorageFactory: K9BackendStorageFactory) : BackendFactory {
|
||||||
|
override fun createBackend(account: LegacyAccount): Backend {
|
||||||
|
val backendStorage = backendStorageFactory.createBackendStorage(account)
|
||||||
|
return DemoBackend(backendStorage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package app.k9mail.featureflag
|
||||||
|
|
||||||
|
import net.thunderbird.core.featureflag.FeatureFlag
|
||||||
|
import net.thunderbird.core.featureflag.FeatureFlagFactory
|
||||||
|
import net.thunderbird.core.featureflag.FeatureFlagKey
|
||||||
|
import net.thunderbird.core.featureflag.toFeatureFlagKey
|
||||||
|
|
||||||
|
class K9FeatureFlagFactory : FeatureFlagFactory {
|
||||||
|
override fun createFeatureCatalog(): List<FeatureFlag> {
|
||||||
|
return listOf(
|
||||||
|
FeatureFlag("archive_marks_as_read".toFeatureFlagKey(), enabled = true),
|
||||||
|
FeatureFlag("new_account_settings".toFeatureFlagKey(), enabled = true),
|
||||||
|
FeatureFlag("disable_font_size_config".toFeatureFlagKey(), enabled = true),
|
||||||
|
FeatureFlag("email_notification_default".toFeatureFlagKey(), enabled = true),
|
||||||
|
FeatureFlag("enable_dropdown_drawer".toFeatureFlagKey(), enabled = true),
|
||||||
|
FeatureFlag("enable_dropdown_drawer_ui".toFeatureFlagKey(), enabled = true),
|
||||||
|
FeatureFlag(FeatureFlagKey.DisplayInAppNotifications, enabled = true),
|
||||||
|
FeatureFlag(FeatureFlagKey.UseNotificationSenderForSystemNotifications, enabled = true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
app-k9mail/src/debug/res/values/app_logo_colors.xml
Normal file
8
app-k9mail/src/debug/res/values/app_logo_colors.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="app_logo_main">#5917ff</color>
|
||||||
|
<color name="app_logo_highlight_light">#7a45ff</color>
|
||||||
|
<color name="app_logo_highlight_dark">#531ad8</color>
|
||||||
|
|
||||||
|
<color name="launcher_icon_background">#e3d9ff</color>
|
||||||
|
</resources>
|
||||||
82
app-k9mail/src/main/AndroidManifest.xml
Normal file
82
app-k9mail/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:installLocation="auto"
|
||||||
|
>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name="app.k9mail.K9App"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.K9.Startup"
|
||||||
|
tools:replace="android:theme"
|
||||||
|
>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.fsck.k9.ui.settings.account.OpenPgpAppSelectDialog"
|
||||||
|
android:configChanges="locale"
|
||||||
|
android:theme="@style/Theme.K9.DayNight.Dialog.Translucent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.fsck.k9.ui.notification.DeleteConfirmationActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/Theme.K9.DayNight.Dialog.Translucent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.startup.InitializationProvider"
|
||||||
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
|
android:exported="false"
|
||||||
|
tools:node="merge"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- We're using on-demand initialization for WorkManager -->
|
||||||
|
<meta-data
|
||||||
|
android:name="androidx.work.WorkManagerInitializer"
|
||||||
|
android:value="androidx.startup"
|
||||||
|
tools:node="remove"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</provider>
|
||||||
|
|
||||||
|
<!-- This component is disabled by default (if possible). It will be enabled programmatically if necessary. -->
|
||||||
|
<!-- IMPORTANT: The component name must be `com.fsck.k9.widget.list.MessageListWidgetProvider` and can't be changed. -->
|
||||||
|
<receiver
|
||||||
|
android:name="com.fsck.k9.widget.list.MessageListWidgetProvider"
|
||||||
|
android:icon="@drawable/message_list_widget_preview"
|
||||||
|
android:label="@string/message_list_widget_label"
|
||||||
|
android:enabled="@bool/home_screen_widgets_enabled"
|
||||||
|
android:exported="false"
|
||||||
|
>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/message_list_widget_info"
|
||||||
|
/>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<!-- This component is disabled by default (if possible). It will be enabled programmatically if necessary. -->
|
||||||
|
<!-- IMPORTANT: The component name must be `com.fsck.k9.provider.UnreadWidgetProvider` and can't be changed. -->
|
||||||
|
<receiver
|
||||||
|
android:name="com.fsck.k9.provider.UnreadWidgetProvider"
|
||||||
|
android:label="@string/unread_widget_label"
|
||||||
|
android:enabled="@bool/home_screen_widgets_enabled"
|
||||||
|
android:exported="false"
|
||||||
|
>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/unread_widget_info"
|
||||||
|
/>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
8
app-k9mail/src/main/kotlin/app/k9mail/K9App.kt
Normal file
8
app-k9mail/src/main/kotlin/app/k9mail/K9App.kt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package app.k9mail
|
||||||
|
|
||||||
|
import net.thunderbird.app.common.BaseApplication
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
|
class K9App : BaseApplication() {
|
||||||
|
override fun provideAppModule(): Module = appModule
|
||||||
|
}
|
||||||
45
app-k9mail/src/main/kotlin/app/k9mail/K9KoinModule.kt
Normal file
45
app-k9mail/src/main/kotlin/app/k9mail/K9KoinModule.kt
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package app.k9mail
|
||||||
|
|
||||||
|
import app.k9mail.auth.K9OAuthConfigurationFactory
|
||||||
|
import app.k9mail.dev.developmentModuleAdditions
|
||||||
|
import app.k9mail.feature.featureModule
|
||||||
|
import app.k9mail.feature.widget.shortcut.LauncherShortcutActivity
|
||||||
|
import app.k9mail.featureflag.K9FeatureFlagFactory
|
||||||
|
import app.k9mail.provider.providerModule
|
||||||
|
import app.k9mail.widget.widgetModule
|
||||||
|
import com.fsck.k9.AppConfig
|
||||||
|
import com.fsck.k9.BuildConfig
|
||||||
|
import com.fsck.k9.DefaultAppConfig
|
||||||
|
import com.fsck.k9.activity.MessageCompose
|
||||||
|
import com.fsck.k9.provider.UnreadWidgetProvider
|
||||||
|
import com.fsck.k9.widget.list.MessageListWidgetProvider
|
||||||
|
import net.thunderbird.app.common.appCommonModule
|
||||||
|
import net.thunderbird.core.common.oauth.OAuthConfigurationFactory
|
||||||
|
import net.thunderbird.core.featureflag.FeatureFlagFactory
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val appModule = module {
|
||||||
|
includes(appCommonModule)
|
||||||
|
|
||||||
|
includes(widgetModule)
|
||||||
|
includes(featureModule)
|
||||||
|
includes(providerModule)
|
||||||
|
|
||||||
|
single(named("ClientInfoAppName")) { BuildConfig.CLIENT_INFO_APP_NAME }
|
||||||
|
single(named("ClientInfoAppVersion")) { BuildConfig.VERSION_NAME }
|
||||||
|
single<AppConfig> { appConfig }
|
||||||
|
single<OAuthConfigurationFactory> { K9OAuthConfigurationFactory() }
|
||||||
|
single<FeatureFlagFactory> { K9FeatureFlagFactory() }
|
||||||
|
|
||||||
|
developmentModuleAdditions()
|
||||||
|
}
|
||||||
|
|
||||||
|
val appConfig = DefaultAppConfig(
|
||||||
|
componentsToDisable = listOf(
|
||||||
|
MessageCompose::class.java,
|
||||||
|
LauncherShortcutActivity::class.java,
|
||||||
|
UnreadWidgetProvider::class.java,
|
||||||
|
MessageListWidgetProvider::class.java,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package app.k9mail.feature
|
||||||
|
|
||||||
|
import app.k9mail.feature.funding.api.FundingSettings
|
||||||
|
import app.k9mail.feature.funding.featureFundingModule
|
||||||
|
import app.k9mail.feature.migration.launcher.featureMigrationModule
|
||||||
|
import app.k9mail.feature.onboarding.migration.onboardingMigrationModule
|
||||||
|
import app.k9mail.feature.telemetry.telemetryModule
|
||||||
|
import net.thunderbird.feature.account.settings.featureAccountSettingsModule
|
||||||
|
import net.thunderbird.feature.mail.message.list.featureMessageListModule
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val featureModule = module {
|
||||||
|
includes(featureAccountSettingsModule)
|
||||||
|
includes(telemetryModule)
|
||||||
|
includes(featureFundingModule)
|
||||||
|
includes(onboardingMigrationModule)
|
||||||
|
includes(featureMigrationModule)
|
||||||
|
includes(featureMessageListModule)
|
||||||
|
|
||||||
|
single<FundingSettings> { K9FundingSettings() }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package app.k9mail.feature
|
||||||
|
|
||||||
|
import app.k9mail.feature.funding.api.FundingSettings
|
||||||
|
import com.fsck.k9.K9
|
||||||
|
|
||||||
|
internal class K9FundingSettings : FundingSettings {
|
||||||
|
override fun getReminderReferenceTimestamp(): Long = K9.fundingReminderReferenceTimestamp
|
||||||
|
|
||||||
|
override fun setReminderReferenceTimestamp(timestamp: Long) {
|
||||||
|
K9.fundingReminderReferenceTimestamp = timestamp
|
||||||
|
K9.saveSettingsAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReminderShownTimestamp() = K9.fundingReminderShownTimestamp
|
||||||
|
|
||||||
|
override fun setReminderShownTimestamp(timestamp: Long) {
|
||||||
|
K9.fundingReminderShownTimestamp = timestamp
|
||||||
|
K9.saveSettingsAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActivityCounterInMillis(): Long = K9.fundingActivityCounterInMillis
|
||||||
|
|
||||||
|
override fun setActivityCounterInMillis(activeTime: Long) {
|
||||||
|
K9.fundingActivityCounterInMillis = activeTime
|
||||||
|
K9.saveSettingsAsync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package app.k9mail.provider
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.fsck.k9.R
|
||||||
|
import com.fsck.k9.preferences.FilePrefixProvider
|
||||||
|
import net.thunderbird.core.common.provider.AppNameProvider
|
||||||
|
import net.thunderbird.core.common.provider.BrandNameProvider
|
||||||
|
|
||||||
|
internal class K9AppNameProvider(
|
||||||
|
context: Context,
|
||||||
|
) : AppNameProvider, BrandNameProvider, FilePrefixProvider {
|
||||||
|
override val appName: String by lazy {
|
||||||
|
context.getString(R.string.app_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val brandName: String by lazy {
|
||||||
|
context.getString(R.string.app_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val filePrefix: String = "k9"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package app.k9mail.provider
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import app.k9mail.core.ui.compose.theme2.k9mail.K9MailTheme2
|
||||||
|
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
|
||||||
|
|
||||||
|
internal class K9FeatureThemeProvider : FeatureThemeProvider {
|
||||||
|
@Composable
|
||||||
|
override fun WithTheme(content: @Composable () -> Unit) {
|
||||||
|
K9MailTheme2 {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun WithTheme(darkTheme: Boolean, content: @Composable () -> Unit) {
|
||||||
|
K9MailTheme2(darkTheme = darkTheme) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package app.k9mail.provider
|
||||||
|
|
||||||
|
import com.fsck.k9.R
|
||||||
|
import net.thunderbird.core.ui.theme.api.ThemeProvider
|
||||||
|
|
||||||
|
internal class K9ThemeProvider : ThemeProvider {
|
||||||
|
override val appThemeResourceId = R.style.Theme_K9_DayNight
|
||||||
|
override val appLightThemeResourceId = R.style.Theme_K9_Light
|
||||||
|
override val appDarkThemeResourceId = R.style.Theme_K9_Dark
|
||||||
|
override val dialogThemeResourceId = R.style.Theme_K9_DayNight_Dialog
|
||||||
|
override val translucentDialogThemeResourceId = R.style.Theme_K9_DayNight_Dialog_Translucent
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package app.k9mail.provider
|
||||||
|
|
||||||
|
import com.fsck.k9.preferences.FilePrefixProvider
|
||||||
|
import net.thunderbird.core.common.provider.AppNameProvider
|
||||||
|
import net.thunderbird.core.common.provider.BrandNameProvider
|
||||||
|
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
|
||||||
|
import net.thunderbird.core.ui.theme.api.ThemeProvider
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.dsl.binds
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
internal val providerModule = module {
|
||||||
|
single {
|
||||||
|
K9AppNameProvider(androidContext())
|
||||||
|
} binds arrayOf(AppNameProvider::class, BrandNameProvider::class, FilePrefixProvider::class)
|
||||||
|
|
||||||
|
single<ThemeProvider> { K9ThemeProvider() }
|
||||||
|
|
||||||
|
single<FeatureThemeProvider> { K9FeatureThemeProvider() }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package app.k9mail.widget
|
||||||
|
|
||||||
|
import app.k9mail.feature.widget.message.list.MessageListWidgetConfig
|
||||||
|
import com.fsck.k9.widget.list.MessageListWidgetProvider
|
||||||
|
|
||||||
|
class K9MessageListWidgetConfig : MessageListWidgetConfig {
|
||||||
|
override val providerClass = MessageListWidgetProvider::class.java
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package app.k9mail.widget
|
||||||
|
|
||||||
|
import app.k9mail.feature.widget.unread.UnreadWidgetConfig
|
||||||
|
import com.fsck.k9.provider.UnreadWidgetProvider
|
||||||
|
|
||||||
|
class K9UnreadWidgetConfig : UnreadWidgetConfig {
|
||||||
|
override val providerClass = UnreadWidgetProvider::class.java
|
||||||
|
}
|
||||||
13
app-k9mail/src/main/kotlin/app/k9mail/widget/WidgetModule.kt
Normal file
13
app-k9mail/src/main/kotlin/app/k9mail/widget/WidgetModule.kt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package app.k9mail.widget
|
||||||
|
|
||||||
|
import app.k9mail.feature.widget.message.list.MessageListWidgetConfig
|
||||||
|
import app.k9mail.feature.widget.unread.UnreadWidgetConfig
|
||||||
|
import net.thunderbird.feature.widget.message.list.featureWidgetMessageListModule
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
internal val widgetModule = module {
|
||||||
|
includes(featureWidgetMessageListModule)
|
||||||
|
|
||||||
|
single<MessageListWidgetConfig> { K9MessageListWidgetConfig() }
|
||||||
|
single<UnreadWidgetConfig> { K9UnreadWidgetConfig() }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.fsck.k9.provider
|
||||||
|
|
||||||
|
import app.k9mail.feature.widget.unread.BaseUnreadWidgetProvider
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IMPORTANT: The fully qualified name for this class must be
|
||||||
|
* `com.fsck.k9.provider.UnreadWidgetProvider`.
|
||||||
|
* Otherwise widgets created with older versions of the app using a different name
|
||||||
|
* will stop working or disappear.
|
||||||
|
*/
|
||||||
|
class UnreadWidgetProvider : BaseUnreadWidgetProvider()
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.fsck.k9.widget.list
|
||||||
|
|
||||||
|
import app.k9mail.feature.widget.message.list.BaseMessageListWidgetProvider
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IMPORTANT: The fully qualified name for this class must be
|
||||||
|
* `com.fsck.k9.widget.list.MessageListWidgetProvider`.
|
||||||
|
* Otherwise widgets created with older versions of the app using a different name
|
||||||
|
* will stop working or disappear.
|
||||||
|
*/
|
||||||
|
class MessageListWidgetProvider : BaseMessageListWidgetProvider()
|
||||||
1543
app-k9mail/src/main/res/raw/changelog_master.xml
Normal file
1543
app-k9mail/src/main/res/raw/changelog_master.xml
Normal file
File diff suppressed because it is too large
Load diff
2
app-k9mail/src/main/res/values-am/strings.xml
Normal file
2
app-k9mail/src/main/res/values-am/strings.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources></resources>
|
||||||
4
app-k9mail/src/main/res/values-ar/strings.xml
Normal file
4
app-k9mail/src/main/res/values-ar/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">بريد K-9</string>
|
||||||
|
</resources>
|
||||||
2
app-k9mail/src/main/res/values-ast/strings.xml
Normal file
2
app-k9mail/src/main/res/values-ast/strings.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources></resources>
|
||||||
4
app-k9mail/src/main/res/values-az/strings.xml
Normal file
4
app-k9mail/src/main/res/values-az/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-be/strings.xml
Normal file
4
app-k9mail/src/main/res/values-be/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">Пошта K-9</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-bg/strings.xml
Normal file
4
app-k9mail/src/main/res/values-bg/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Поща</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-bn/strings.xml
Normal file
4
app-k9mail/src/main/res/values-bn/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">K-9 মেইল</string>
|
||||||
|
</resources>
|
||||||
3
app-k9mail/src/main/res/values-br/strings.xml
Normal file
3
app-k9mail/src/main/res/values-br/strings.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-bs/strings.xml
Normal file
4
app-k9mail/src/main/res/values-bs/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-ca/strings.xml
Normal file
4
app-k9mail/src/main/res/values-ca/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-co/strings.xml
Normal file
4
app-k9mail/src/main/res/values-co/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-cs/strings.xml
Normal file
4
app-k9mail/src/main/res/values-cs/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-cy/strings.xml
Normal file
4
app-k9mail/src/main/res/values-cy/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-da/strings.xml
Normal file
4
app-k9mail/src/main/res/values-da/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-de/strings.xml
Normal file
4
app-k9mail/src/main/res/values-de/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-el/strings.xml
Normal file
4
app-k9mail/src/main/res/values-el/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-en-rGB/strings.xml
Normal file
4
app-k9mail/src/main/res/values-en-rGB/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
2
app-k9mail/src/main/res/values-enm/strings.xml
Normal file
2
app-k9mail/src/main/res/values-enm/strings.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources></resources>
|
||||||
4
app-k9mail/src/main/res/values-eo/strings.xml
Normal file
4
app-k9mail/src/main/res/values-eo/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Retpoŝtilo</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-es/strings.xml
Normal file
4
app-k9mail/src/main/res/values-es/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-et/strings.xml
Normal file
4
app-k9mail/src/main/res/values-et/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-eu/strings.xml
Normal file
4
app-k9mail/src/main/res/values-eu/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-fa/strings.xml
Normal file
4
app-k9mail/src/main/res/values-fa/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">نامهٔ کی۹</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-fi/strings.xml
Normal file
4
app-k9mail/src/main/res/values-fi/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-fr/strings.xml
Normal file
4
app-k9mail/src/main/res/values-fr/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">Courriel K-9</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-fy/strings.xml
Normal file
4
app-k9mail/src/main/res/values-fy/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-ga/strings.xml
Normal file
4
app-k9mail/src/main/res/values-ga/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">K-9 Post</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-gd/strings.xml
Normal file
4
app-k9mail/src/main/res/values-gd/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">Post K-9</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-gl/strings.xml
Normal file
4
app-k9mail/src/main/res/values-gl/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-gu/strings.xml
Normal file
4
app-k9mail/src/main/res/values-gu/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">કે-૯ મેલ</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-hi/strings.xml
Normal file
4
app-k9mail/src/main/res/values-hi/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">के-9 मेल</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-hr/strings.xml
Normal file
4
app-k9mail/src/main/res/values-hr/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-hu/strings.xml
Normal file
4
app-k9mail/src/main/res/values-hu/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-hy/strings.xml
Normal file
4
app-k9mail/src/main/res/values-hy/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Նամակ</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-in/strings.xml
Normal file
4
app-k9mail/src/main/res/values-in/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">Surel K-9</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-is/strings.xml
Normal file
4
app-k9mail/src/main/res/values-is/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 - Póstur</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-it/strings.xml
Normal file
4
app-k9mail/src/main/res/values-it/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 Mail</string>
|
||||||
|
</resources>
|
||||||
4
app-k9mail/src/main/res/values-iw/strings.xml
Normal file
4
app-k9mail/src/main/res/values-iw/strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">K-9 דוא\"ל</string>
|
||||||
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue