mirror of https://github.com/tasks/tasks
Compare commits
363 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
fbfcbdc555 | 2 weeks ago |
|
|
51e347f22b | 2 weeks ago |
|
|
d0c28baf7b | 2 weeks ago |
|
|
3d4d44849e | 3 weeks ago |
|
|
dfa41c515a | 3 weeks ago |
|
|
a539b3a3e4 | 3 weeks ago |
|
|
8d6de19b2a | 3 weeks ago |
|
|
2a6e1638c9 | 3 weeks ago |
|
|
9190930745 | 3 weeks ago |
|
|
40961dad87 | 3 weeks ago |
|
|
9fbe27345d | 4 weeks ago |
|
|
5f67e0ea3a | 4 weeks ago |
|
|
d51171b17e | 4 weeks ago |
|
|
b657773a2d | 4 weeks ago |
|
|
d84effc447 | 4 weeks ago |
|
|
21db540614 | 4 weeks ago |
|
|
8e9f27c46e | 4 weeks ago |
|
|
94ad2a381e | 4 weeks ago |
|
|
747928c8c7 | 4 weeks ago |
|
|
9a28f1062b | 4 weeks ago |
|
|
ffe749bf0c | 4 weeks ago |
|
|
b0ae0129ae | 4 weeks ago |
|
|
6715369d93 | 4 weeks ago |
|
|
4efb678699 | 4 weeks ago |
|
|
581b789a0b | 4 weeks ago |
|
|
15d8b3aa59 | 4 weeks ago |
|
|
dcd5d8c094 | 4 weeks ago |
|
|
f3253e6188 | 4 weeks ago |
|
|
34b0c62ef8 | 4 weeks ago |
|
|
3754196714 | 4 weeks ago |
|
|
b94a91efbe | 4 weeks ago |
|
|
2c6066c378 | 4 weeks ago |
|
|
03e15a8c35 | 4 weeks ago |
|
|
e63add73bc | 4 weeks ago |
|
|
bf676bcea7 | 4 weeks ago |
|
|
6de8fe2fa0 | 4 weeks ago |
|
|
5907f27172 | 4 weeks ago |
|
|
38119d4560 | 4 weeks ago |
|
|
52848a5308 | 4 weeks ago |
|
|
4c492120b3 | 4 weeks ago |
|
|
20e995b19b | 4 weeks ago |
|
|
2b63e33de2 | 4 weeks ago |
|
|
5980bd497d | 2 months ago |
|
|
e16b5cd6cd | 2 months ago |
|
|
3d9945c798 | 2 months ago |
|
|
5c9eb1c35f | 2 months ago |
|
|
6b594a3213 | 2 months ago |
|
|
9e0e01f89b | 2 months ago |
|
|
932b8b0540 | 3 months ago |
|
|
152a9684e5 | 3 months ago |
|
|
88c817b770 | 3 months ago |
|
|
a368960073 | 3 months ago |
|
|
4f1cc5ab8e | 3 months ago |
|
|
17f54b6d32 | 3 months ago |
|
|
6a44bed0e8 | 3 months ago |
|
|
7fa90396b3 | 3 months ago |
|
|
d1df39d12c | 3 months ago |
|
|
d9ddd45f13 | 3 months ago |
|
|
27b21118eb | 3 months ago |
|
|
f725365f87 | 3 months ago |
|
|
7377e4672d | 3 months ago |
|
|
8c90b1ec87 | 3 months ago |
|
|
5b50f45a5b | 3 months ago |
|
|
882338f554 | 3 months ago |
|
|
930e980550 | 3 months ago |
|
|
fb243c7aaf | 3 months ago |
|
|
c3842fd2f7 | 3 months ago |
|
|
81ecb322e9 | 3 months ago |
|
|
1b386458b8 | 3 months ago |
|
|
7dca092831 | 3 months ago |
|
|
2749b029c5 | 3 months ago |
|
|
0cb19221c4 | 3 months ago |
|
|
d900f72a5c | 3 months ago |
|
|
0d8979b72c | 3 months ago |
|
|
703322f510 | 3 months ago |
|
|
192351a4b8 | 3 months ago |
|
|
844a3a0ff8 | 3 months ago |
|
|
e6bbc8d361 | 3 months ago |
|
|
04ab41f622 | 3 months ago |
|
|
4946b0ca06 | 3 months ago |
|
|
2c29194ff2 | 3 months ago |
|
|
1aaaad86da | 3 months ago |
|
|
68601873fd | 3 months ago |
|
|
c9721790ce | 3 months ago |
|
|
6f16a29fd7 | 3 months ago |
|
|
a501b81bfc | 3 months ago |
|
|
fded7fbdd5 | 3 months ago |
|
|
3ff4a2339b | 3 months ago |
|
|
e400594e5b | 3 months ago |
|
|
8bbbc1dcac | 3 months ago |
|
|
38d27b262a | 3 months ago |
|
|
b61842646b | 3 months ago |
|
|
29cbb33a42 | 3 months ago |
|
|
2418b664e9 | 3 months ago |
|
|
0e3803c28d | 3 months ago |
|
|
c0bb7b306a | 3 months ago |
|
|
903412fdea | 3 months ago |
|
|
12b1127e6b | 3 months ago |
|
|
c3ce7a43fb | 3 months ago |
|
|
a391dff9bc | 3 months ago |
|
|
28e12110fa | 3 months ago |
|
|
0bba2c4a63 | 3 months ago |
|
|
700421c5ce | 3 months ago |
|
|
d4a742b136 | 3 months ago |
|
|
585967c601 | 3 months ago |
|
|
d279f7c42e | 3 months ago |
|
|
5cfbe9c8cb | 3 months ago |
|
|
f591b1846c | 3 months ago |
|
|
3401a59716 | 3 months ago |
|
|
d0328b378a | 3 months ago |
|
|
f9d859a33e | 3 months ago |
|
|
5a1560e513 | 3 months ago |
|
|
655cdc1a9d | 3 months ago |
|
|
f2235e6aa6 | 3 months ago |
|
|
09baafb47f | 3 months ago |
|
|
2d0cfaa04d | 3 months ago |
|
|
e60d516fcf | 3 months ago |
|
|
9ba82c3a01 | 3 months ago |
|
|
b7abdfe2ea | 3 months ago |
|
|
3da6f67ace | 4 months ago |
|
|
eec3ae447a | 4 months ago |
|
|
7a9a27eae0 | 4 months ago |
|
|
05b5f1470a | 4 months ago |
|
|
2a94af70fd | 4 months ago |
|
|
464903bf4d | 4 months ago |
|
|
0542f24c29 | 4 months ago |
|
|
0fe834b46c | 4 months ago |
|
|
997810af4c | 4 months ago |
|
|
6829f3f690 | 4 months ago |
|
|
cec5c1e4b8 | 4 months ago |
|
|
d3a12b039a | 4 months ago |
|
|
67dcc1db38 | 4 months ago |
|
|
83ae176288 | 4 months ago |
|
|
5e535b6d46 | 4 months ago |
|
|
5c124047e8 | 4 months ago |
|
|
8a332c8b2a | 4 months ago |
|
|
9d6a925fca | 4 months ago |
|
|
8cc28aa88b | 4 months ago |
|
|
99ea6cb0eb | 4 months ago |
|
|
a698236f4d | 4 months ago |
|
|
ad616472b3 | 4 months ago |
|
|
d5cda9e84b | 4 months ago |
|
|
9808ca1745 | 4 months ago |
|
|
0b76caa9a8 | 4 months ago |
|
|
07ac9f9ead | 4 months ago |
|
|
977edf4d8d | 4 months ago |
|
|
071d670c6d | 4 months ago |
|
|
eb89cc689a | 4 months ago |
|
|
a5c73ccc24 | 4 months ago |
|
|
51884d46f2 | 4 months ago |
|
|
101d7f2357 | 4 months ago |
|
|
75563b6a61 | 4 months ago |
|
|
3028d492b2 | 4 months ago |
|
|
ab2fc34e98 | 4 months ago |
|
|
852ac708b5 | 4 months ago |
|
|
092f357719 | 4 months ago |
|
|
ad1ace8fbf | 4 months ago |
|
|
204f49fc25 | 4 months ago |
|
|
9ef95291c8 | 4 months ago |
|
|
3e034ab91f | 4 months ago |
|
|
6f89ac3b93 | 4 months ago |
|
|
7d2ebf9cdf | 4 months ago |
|
|
16011b1963 | 4 months ago |
|
|
2f6348c53d | 4 months ago |
|
|
566c22c17e | 4 months ago |
|
|
2c33be700a | 4 months ago |
|
|
7a24f43387 | 4 months ago |
|
|
370ac149d3 | 4 months ago |
|
|
4c851ce7f3 | 4 months ago |
|
|
7c78854663 | 4 months ago |
|
|
d05730399d | 4 months ago |
|
|
c8f564d2d5 | 4 months ago |
|
|
65db4ab926 | 4 months ago |
|
|
1476e7fb27 | 4 months ago |
|
|
2ee0939564 | 4 months ago |
|
|
4c530a5de3 | 4 months ago |
|
|
fcd62c6801 | 4 months ago |
|
|
aedd29982a | 4 months ago |
|
|
3a37d6481e | 4 months ago |
|
|
c5f8583146 | 4 months ago |
|
|
9d96bed5b3 | 4 months ago |
|
|
2f268c8c70 | 4 months ago |
|
|
130a29d7e3 | 4 months ago |
|
|
dcb69394be | 4 months ago |
|
|
2cf3438e07 | 4 months ago |
|
|
06e9da41d6 | 4 months ago |
|
|
7b34e33c0e | 4 months ago |
|
|
be51651779 | 4 months ago |
|
|
627b05a575 | 4 months ago |
|
|
8207f30c5f | 5 months ago |
|
|
6811677d21 | 5 months ago |
|
|
9d88c5b3a0 | 5 months ago |
|
|
1f24a371fb | 5 months ago |
|
|
877a2cd6a5 | 5 months ago |
|
|
299b5b4d21 | 5 months ago |
|
|
e6320d42a7 | 5 months ago |
|
|
84c36a1a90 | 5 months ago |
|
|
73c0e38991 | 5 months ago |
|
|
773e822f14 | 5 months ago |
|
|
6f167b5ae0 | 5 months ago |
|
|
ba0cd26abc | 5 months ago |
|
|
3450db4006 | 5 months ago |
|
|
7c2cf38788 | 5 months ago |
|
|
e576a48eba | 5 months ago |
|
|
803593a3a7 | 5 months ago |
|
|
c4fc7fbadb | 5 months ago |
|
|
75d53fb8ac | 5 months ago |
|
|
769802c10a | 5 months ago |
|
|
14ff0086fa | 5 months ago |
|
|
761d4afeef | 5 months ago |
|
|
c7336589cd | 5 months ago |
|
|
e30c583d5a | 5 months ago |
|
|
38527aef0a | 5 months ago |
|
|
c9cdc4d50f | 5 months ago |
|
|
80753f607c | 5 months ago |
|
|
32cb067ffd | 5 months ago |
|
|
e93d0735d4 | 5 months ago |
|
|
103e7eaa60 | 5 months ago |
|
|
a490307251 | 5 months ago |
|
|
976df68671 | 5 months ago |
|
|
384f6e4604 | 5 months ago |
|
|
b68439b0e7 | 5 months ago |
|
|
3611593307 | 5 months ago |
|
|
c7a7384cf5 | 5 months ago |
|
|
f89789dd10 | 5 months ago |
|
|
91da4bc661 | 5 months ago |
|
|
30abeba683 | 5 months ago |
|
|
8f567a153a | 5 months ago |
|
|
2fbffa20cc | 5 months ago |
|
|
a071b05a71 | 5 months ago |
|
|
63dbb48d96 | 5 months ago |
|
|
e2c65c06a1 | 5 months ago |
|
|
03b1d78feb | 5 months ago |
|
|
fe72301c55 | 5 months ago |
|
|
ee40b72b02 | 5 months ago |
|
|
6181351db7 | 5 months ago |
|
|
8263ab2935 | 5 months ago |
|
|
38dbbe379b | 5 months ago |
|
|
58d5eea978 | 5 months ago |
|
|
f902ff38b0 | 5 months ago |
|
|
7c970eec95 | 5 months ago |
|
|
9ec448aedd | 5 months ago |
|
|
2fb1bb4873 | 5 months ago |
|
|
c64e581fd4 | 5 months ago |
|
|
36ec47e9bd | 5 months ago |
|
|
2942554ec8 | 5 months ago |
|
|
b6b624ce5b | 5 months ago |
|
|
1267c803c6 | 5 months ago |
|
|
7a17943142 | 5 months ago |
|
|
d73b8496cb | 5 months ago |
|
|
eda6eeaf62 | 5 months ago |
|
|
6d89f2cb02 | 5 months ago |
|
|
273fbf9153 | 5 months ago |
|
|
2b1ad31f76 | 5 months ago |
|
|
fdd1fc4989 | 5 months ago |
|
|
ff6a8ae0f1 | 5 months ago |
|
|
d1da6dc970 | 5 months ago |
|
|
a3dac4a397 | 5 months ago |
|
|
30060d8faf | 5 months ago |
|
|
a299363fe8 | 5 months ago |
|
|
36b20f47fd | 6 months ago |
|
|
5b1aff00df | 6 months ago |
|
|
f53aec3e8c | 6 months ago |
|
|
079d7867f1 | 6 months ago |
|
|
8b99e8feb2 | 6 months ago |
|
|
1a9b371bda | 6 months ago |
|
|
dfc311ff31 | 6 months ago |
|
|
cbcb812150 | 6 months ago |
|
|
70793f2433 | 6 months ago |
|
|
a198846902 | 6 months ago |
|
|
86a3b2b426 | 6 months ago |
|
|
bbac4da7d0 | 6 months ago |
|
|
f4e0d519d7 | 6 months ago |
|
|
9a584c851b | 6 months ago |
|
|
704edaa0ab | 6 months ago |
|
|
71833adf21 | 6 months ago |
|
|
4aad9bf00e | 6 months ago |
|
|
d4b1a0dd09 | 6 months ago |
|
|
0f1508f59a | 6 months ago |
|
|
266fe1281e | 6 months ago |
|
|
7e99762814 | 6 months ago |
|
|
56ec24d2f9 | 6 months ago |
|
|
fad3ab6ce3 | 6 months ago |
|
|
522bd6e304 | 6 months ago |
|
|
119572971d | 6 months ago |
|
|
48270d6f2c | 6 months ago |
|
|
44d15556c6 | 6 months ago |
|
|
33611d12bd | 6 months ago |
|
|
caa5916ad7 | 6 months ago |
|
|
821e8e0ee4 | 6 months ago |
|
|
fe4bd73d62 | 6 months ago |
|
|
b0493fdd7d | 6 months ago |
|
|
f330daa764 | 6 months ago |
|
|
017dd17021 | 6 months ago |
|
|
aa535981c3 | 6 months ago |
|
|
6f39614b5b | 6 months ago |
|
|
d33d87b6cb | 6 months ago |
|
|
a828d510e3 | 6 months ago |
|
|
898f84e3c8 | 6 months ago |
|
|
a1711fa0ea | 6 months ago |
|
|
542ba69870 | 6 months ago |
|
|
01d07cccbd | 6 months ago |
|
|
3e8838bdb6 | 6 months ago |
|
|
ac4c610841 | 6 months ago |
|
|
6bea199a75 | 6 months ago |
|
|
1efb8c8ee0 | 6 months ago |
|
|
d2c23a79de | 6 months ago |
|
|
0091f80945 | 6 months ago |
|
|
5cb770e722 | 6 months ago |
|
|
81759305c5 | 6 months ago |
|
|
1692a98d3d | 6 months ago |
|
|
9174855f2f | 6 months ago |
|
|
9632ea61d2 | 6 months ago |
|
|
e2ca2a2251 | 6 months ago |
|
|
dad35aafd3 | 6 months ago |
|
|
0735cb5e1d | 6 months ago |
|
|
55c8ab6e3a | 6 months ago |
|
|
5c4b345695 | 6 months ago |
|
|
2c6b1644dc | 6 months ago |
|
|
efaa2cf472 | 6 months ago |
|
|
c4e160a21a | 6 months ago |
|
|
7d6c20aec0 | 6 months ago |
|
|
3960b57242 | 7 months ago |
|
|
0e5e6e9b05 | 7 months ago |
|
|
147c9f44d2 | 7 months ago |
|
|
ad7141a1c9 | 7 months ago |
|
|
6a99e75115 | 7 months ago |
|
|
b4f9770344 | 7 months ago |
|
|
f6ffcf8397 | 7 months ago |
|
|
a23c541195 | 7 months ago |
|
|
dea9783ce5 | 7 months ago |
|
|
93e7a16850 | 7 months ago |
|
|
760bd1d58b | 7 months ago |
|
|
e580ced066 | 7 months ago |
|
|
169140cc0b | 7 months ago |
|
|
8f5c41051b | 7 months ago |
|
|
a2098c1876 | 7 months ago |
|
|
d446e009b4 | 7 months ago |
|
|
35161972c1 | 7 months ago |
|
|
6a8a9dec80 | 7 months ago |
|
|
627ada6679 | 7 months ago |
|
|
c996462e32 | 7 months ago |
|
|
cd5a89960d | 7 months ago |
|
|
805b7d23df | 7 months ago |
|
|
a4ca8b28aa | 7 months ago |
|
|
a519a06c3b | 7 months ago |
|
|
c22384d789 | 7 months ago |
|
|
247c286e61 | 7 months ago |
|
|
15c7bc2fc0 | 7 months ago |
|
|
08fa635ce9 | 7 months ago |
|
|
2946133eb2 | 7 months ago |
|
|
e8f6276b5b | 7 months ago |
|
|
83e8044bc5 | 7 months ago |
|
|
892beb83f7 | 7 months ago |
|
|
a623a7bd97 | 7 months ago |
|
|
98ae90a6ad | 7 months ago |
|
|
9b1078dc16 | 7 months ago |
|
|
cb220654cf | 7 months ago |
|
|
65533d3675 | 7 months ago |
|
|
cdb11e631f | 7 months ago |
|
|
1fd0fe232a | 7 months ago |
|
|
ec05fed425 | 7 months ago |
|
|
1d7cc3794a | 7 months ago |
@ -1 +1 @@
|
||||
3.3.6
|
||||
3.4.7
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
gem "abbrev"
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package org.tasks.data
|
||||
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.tasks.data.dao.CaldavDao
|
||||
import org.tasks.data.entity.CaldavAccount
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class CaldavDaoExtensionsTest : InjectingTestCase() {
|
||||
@Inject lateinit var caldavDao: CaldavDao
|
||||
|
||||
@Test
|
||||
fun getLocalListCreatesAccountIfNeeded() = runBlocking {
|
||||
withTimeout(5000L) {
|
||||
assertTrue(caldavDao.getAccounts().isEmpty())
|
||||
caldavDao.getLocalList()
|
||||
assertTrue(caldavDao.getAccounts(CaldavAccount.TYPE_LOCAL).isNotEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest>
|
||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application/>
|
||||
<application tools:ignore="MissingApplicationIcon">
|
||||
<activity
|
||||
android:name=".auth.MicrosoftAuthenticationActivity"
|
||||
android:theme="@style/TranslucentDialog"/>
|
||||
<activity
|
||||
android:name="net.openid.appauth.RedirectUriReceiverActivity"
|
||||
android:exported="true"
|
||||
tools:node="merge">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data
|
||||
android:host="${applicationId}"
|
||||
android:scheme="msauth" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
package org.tasks.auth
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import net.openid.appauth.AuthorizationServiceConfiguration
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
data class IdentityProvider(
|
||||
val name: String,
|
||||
val discoveryEndpoint: Uri,
|
||||
val clientId: String,
|
||||
val redirectUri: Uri,
|
||||
val scope: String
|
||||
) {
|
||||
suspend fun retrieveConfig(): AuthorizationServiceConfiguration {
|
||||
return suspendCoroutine { cont ->
|
||||
AuthorizationServiceConfiguration.fetchFromUrl(discoveryEndpoint) { serviceConfiguration, ex ->
|
||||
cont.resumeWith(
|
||||
when {
|
||||
ex != null -> Result.failure(ex)
|
||||
serviceConfiguration != null -> Result.success(serviceConfiguration)
|
||||
else -> Result.failure(IllegalStateException())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val MICROSOFT = IdentityProvider(
|
||||
"Microsoft",
|
||||
"https://login.microsoftonline.com/consumers/v2.0/.well-known/openid-configuration".toUri(),
|
||||
"9d4babd5-e7ba-4286-ba4b-17274495a901",
|
||||
"msauth://org.tasks/8wnYBRqh5nnQgFzbIXfxXSs41xE%3D".toUri(),
|
||||
"user.read Tasks.ReadWrite openid offline_access email"
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,157 @@
|
||||
package org.tasks.auth
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color.Companion.White
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.openid.appauth.AuthState
|
||||
import net.openid.appauth.AuthorizationException
|
||||
import net.openid.appauth.AuthorizationResponse
|
||||
import net.openid.appauth.AuthorizationServiceDiscovery
|
||||
import okhttp3.Request
|
||||
import org.json.JSONObject
|
||||
import org.tasks.R
|
||||
import org.tasks.analytics.Constants
|
||||
import org.tasks.analytics.Firebase
|
||||
import org.tasks.data.UUIDHelper
|
||||
import org.tasks.data.dao.CaldavDao
|
||||
import org.tasks.data.entity.CaldavAccount
|
||||
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_MICROSOFT
|
||||
import org.tasks.http.HttpClientFactory
|
||||
import org.tasks.jobs.WorkManager
|
||||
import org.tasks.preferences.fragments.TasksAccountViewModel.Companion.getStringOrNull
|
||||
import org.tasks.security.KeyStoreEncryption
|
||||
import org.tasks.sync.SyncAdapters
|
||||
import org.tasks.sync.microsoft.requestTokenExchange
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MicrosoftAuthenticationActivity : ComponentActivity() {
|
||||
|
||||
@Inject lateinit var caldavDao: CaldavDao
|
||||
@Inject lateinit var encryption: KeyStoreEncryption
|
||||
@Inject lateinit var httpClientFactory: HttpClientFactory
|
||||
@Inject lateinit var firebase: Firebase
|
||||
@Inject lateinit var syncAdapters: SyncAdapters
|
||||
@Inject lateinit var workManager: WorkManager
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val authState = AuthState(
|
||||
AuthorizationResponse.fromIntent(intent),
|
||||
AuthorizationException.fromIntent(intent)
|
||||
)
|
||||
authState.authorizationException?.let {
|
||||
error(it.message ?: "Authentication failed")
|
||||
return
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val (resp, ex) = requestTokenExchange(authState.lastAuthorizationResponse!!)
|
||||
authState.update(resp, ex)
|
||||
if (authState.isAuthorized) {
|
||||
val email = getEmail(authState.accessToken) ?: run {
|
||||
error("Failed to fetch profile")
|
||||
return@launch
|
||||
}
|
||||
caldavDao
|
||||
.getAccount(TYPE_MICROSOFT, email)
|
||||
?.let {
|
||||
caldavDao.update(
|
||||
it.copy(password = encryption.encrypt(authState.jsonSerializeString()))
|
||||
)
|
||||
}
|
||||
?: caldavDao
|
||||
.insert(
|
||||
CaldavAccount(
|
||||
uuid = UUIDHelper.newUUID(),
|
||||
name = email,
|
||||
username = email,
|
||||
password = encryption.encrypt(authState.jsonSerializeString()),
|
||||
accountType = TYPE_MICROSOFT,
|
||||
)
|
||||
)
|
||||
.also {
|
||||
firebase.logEvent(
|
||||
R.string.event_sync_add_account,
|
||||
R.string.param_type to Constants.SYNC_TYPE_MICROSOFT
|
||||
)
|
||||
}
|
||||
syncAdapters.sync(true)
|
||||
workManager.updateBackgroundSync()
|
||||
finish()
|
||||
} else {
|
||||
error(ex?.message ?: "Token exchange failed")
|
||||
}
|
||||
}
|
||||
setContent {
|
||||
var showDialog by remember { mutableStateOf(true) }
|
||||
if (showDialog) {
|
||||
Dialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.background(White, shape = RoundedCornerShape(8.dp))
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEmail(accessToken: String?): String? = withContext(Dispatchers.IO) {
|
||||
if (accessToken == null) {
|
||||
return@withContext null
|
||||
}
|
||||
val discovery = AuthorizationServiceDiscovery(
|
||||
JSONObject(
|
||||
intent.getStringExtra(EXTRA_SERVICE_DISCOVERY)!!
|
||||
)
|
||||
)
|
||||
val userInfo = httpClientFactory
|
||||
.newClient(foreground = false)
|
||||
.newCall(
|
||||
Request.Builder()
|
||||
.url(discovery.userinfoEndpoint!!.toString())
|
||||
.addHeader("Authorization", "Bearer $accessToken")
|
||||
.build()
|
||||
)
|
||||
.execute()
|
||||
val response = userInfo.body?.string() ?: return@withContext null
|
||||
JSONObject(response).getStringOrNull("email")
|
||||
}
|
||||
|
||||
private fun error(message: String) {
|
||||
Toast.makeText(this@MicrosoftAuthenticationActivity, message, LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_SERVICE_DISCOVERY = "extra_service_discovery"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package org.tasks.sync.microsoft
|
||||
|
||||
import android.content.Context
|
||||
import net.openid.appauth.AuthState
|
||||
import net.openid.appauth.AuthorizationException
|
||||
import net.openid.appauth.AuthorizationResponse
|
||||
import net.openid.appauth.AuthorizationService
|
||||
import net.openid.appauth.TokenRequest
|
||||
import net.openid.appauth.TokenResponse
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
suspend fun Context.requestTokenRefresh(state: AuthState) =
|
||||
requestToken(state.createTokenRefreshRequest())
|
||||
|
||||
suspend fun Context.requestTokenExchange(response: AuthorizationResponse) =
|
||||
requestToken(response.createTokenExchangeRequest())
|
||||
|
||||
private suspend fun Context.requestToken(tokenRequest: TokenRequest): Pair<TokenResponse?, AuthorizationException?> {
|
||||
val authService = AuthorizationService(this)
|
||||
return try {
|
||||
suspendCoroutine { cont ->
|
||||
authService.performTokenRequest(tokenRequest) { response, ex ->
|
||||
cont.resumeWith(Result.success(Pair(response, ex)))
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
authService.dispose()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package org.tasks.sync.microsoft
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import net.openid.appauth.AppAuthConfiguration
|
||||
import net.openid.appauth.AuthorizationRequest
|
||||
import net.openid.appauth.AuthorizationService
|
||||
import net.openid.appauth.ResponseTypeValues
|
||||
import net.openid.appauth.browser.AnyBrowserMatcher
|
||||
import net.openid.appauth.connectivity.DefaultConnectionBuilder
|
||||
import org.tasks.BuildConfig
|
||||
import org.tasks.auth.DebugConnectionBuilder
|
||||
import org.tasks.auth.IdentityProvider
|
||||
import org.tasks.auth.MicrosoftAuthenticationActivity
|
||||
import org.tasks.auth.MicrosoftAuthenticationActivity.Companion.EXTRA_SERVICE_DISCOVERY
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class MicrosoftSignInViewModel @Inject constructor(
|
||||
private val debugConnectionBuilder: DebugConnectionBuilder,
|
||||
) : ViewModel() {
|
||||
fun signIn(activity: Activity) {
|
||||
viewModelScope.launch {
|
||||
val idp = IdentityProvider.MICROSOFT
|
||||
val serviceConfig = idp.retrieveConfig()
|
||||
val authRequest = AuthorizationRequest
|
||||
.Builder(
|
||||
serviceConfig,
|
||||
idp.clientId,
|
||||
ResponseTypeValues.CODE,
|
||||
idp.redirectUri
|
||||
)
|
||||
.setScope(idp.scope)
|
||||
.setPrompt(AuthorizationRequest.Prompt.SELECT_ACCOUNT)
|
||||
.build()
|
||||
val intent = Intent(activity, MicrosoftAuthenticationActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.putExtra(
|
||||
EXTRA_SERVICE_DISCOVERY,
|
||||
serviceConfig.discoveryDoc!!.docJson.toString()
|
||||
)
|
||||
|
||||
val authorizationService = AuthorizationService(
|
||||
activity,
|
||||
AppAuthConfiguration.Builder()
|
||||
.setBrowserMatcher(AnyBrowserMatcher.INSTANCE)
|
||||
.setConnectionBuilder(
|
||||
if (BuildConfig.DEBUG) {
|
||||
debugConnectionBuilder
|
||||
} else {
|
||||
DefaultConnectionBuilder.INSTANCE
|
||||
}
|
||||
)
|
||||
.build()
|
||||
)
|
||||
authorizationService.performAuthorizationRequest(
|
||||
authRequest,
|
||||
PendingIntent.getActivity(
|
||||
activity,
|
||||
authRequest.hashCode(),
|
||||
intent,
|
||||
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
||||
),
|
||||
authorizationService.createCustomTabsIntentBuilder()
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package org.tasks.sync.microsoft
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import net.openid.appauth.AuthState
|
||||
import org.tasks.data.entity.CaldavAccount
|
||||
import org.tasks.security.KeyStoreEncryption
|
||||
import javax.inject.Inject
|
||||
|
||||
class MicrosoftTokenProvider @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val encryption: KeyStoreEncryption,
|
||||
) {
|
||||
suspend fun getToken(account: CaldavAccount): String {
|
||||
val authState = encryption.decrypt(account.password)?.let { AuthState.jsonDeserialize(it) }
|
||||
?: throw RuntimeException("Missing credentials")
|
||||
if (authState.needsTokenRefresh) {
|
||||
val (token, ex) = context.requestTokenRefresh(authState)
|
||||
authState.update(token, ex)
|
||||
if (authState.isAuthorized) {
|
||||
account.password = encryption.encrypt(authState.jsonSerializeString())
|
||||
}
|
||||
}
|
||||
if (!authState.isAuthorized) {
|
||||
throw RuntimeException("Needs authentication")
|
||||
}
|
||||
return authState.accessToken!!
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
{
|
||||
"client_id" : "9d4babd5-e7ba-4286-ba4b-17274495a901",
|
||||
"authorization_user_agent" : "DEFAULT",
|
||||
"redirect_uri" : "msauth://org.tasks/xVwQTvk42gGm0o6zNvelaYloFcs%3D",
|
||||
"account_mode" : "MULTIPLE",
|
||||
"authorities" : [
|
||||
{
|
||||
"type": "AAD",
|
||||
"audience": {
|
||||
"type": "AzureADandPersonalMicrosoftAccount",
|
||||
"tenant_id": "common"
|
||||
}
|
||||
}
|
||||
],
|
||||
"logging": {
|
||||
"level": "info",
|
||||
"logcat_enabled": true,
|
||||
"pii_enabled": false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package org.tasks.sync.microsoft
|
||||
|
||||
import android.content.Context
|
||||
import com.microsoft.identity.client.AcquireTokenSilentParameters
|
||||
import com.microsoft.identity.client.PublicClientApplication
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.tasks.R
|
||||
import org.tasks.data.entity.CaldavAccount
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class MicrosoftTokenProvider @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
fun getToken(account: CaldavAccount): String {
|
||||
val app = PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||
context,
|
||||
R.raw.microsoft_config
|
||||
)
|
||||
|
||||
val result = try {
|
||||
val msalAccount = app.accounts.firstOrNull { it.username == account.username }
|
||||
?: throw RuntimeException("No matching account found")
|
||||
|
||||
val parameters = AcquireTokenSilentParameters.Builder()
|
||||
.withScopes(MicrosoftSignInViewModel.scopes)
|
||||
.forAccount(msalAccount)
|
||||
.fromAuthority(msalAccount.authority)
|
||||
.forceRefresh(true)
|
||||
.build()
|
||||
|
||||
app.acquireTokenSilent(parameters)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
throw RuntimeException("Authentication failed: ${e.message}")
|
||||
}
|
||||
return result.accessToken
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.tasks.caldav.property
|
||||
|
||||
import at.bitfire.dav4jvm.Property
|
||||
import at.bitfire.dav4jvm.PropertyFactory
|
||||
import at.bitfire.dav4jvm.XmlUtils
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import timber.log.Timber
|
||||
|
||||
data class CalendarIcon(
|
||||
val icon: String,
|
||||
): Property {
|
||||
companion object Companion {
|
||||
@JvmField
|
||||
val NAME = Property.Name(PropertyUtils.NS_TASKS, "x-calendar-icon")
|
||||
}
|
||||
|
||||
object Factory: PropertyFactory {
|
||||
|
||||
override fun getName() = NAME
|
||||
|
||||
override fun create(parser: XmlPullParser): CalendarIcon? {
|
||||
XmlUtils.readText(parser)?.takeIf { it.isNotBlank() }?.let {
|
||||
try {
|
||||
return CalendarIcon(it)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e, "Couldn't parse icon: $it")
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,6 @@
|
||||
package org.tasks.caldav.property
|
||||
|
||||
import at.bitfire.dav4jvm.PropertyFactory
|
||||
import at.bitfire.dav4jvm.PropertyRegistry
|
||||
|
||||
object PropertyUtils {
|
||||
const val NS_TASKS = "http://org.tasks/ns/"
|
||||
const val NS_OWNCLOUD = "http://owncloud.org/ns"
|
||||
|
||||
fun PropertyRegistry.register(vararg factories: PropertyFactory) = register(factories.toList())
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
package org.tasks.compose.home
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@Composable
|
||||
fun SystemBarScrim(
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = MaterialTheme.colorScheme.background,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(color.copy(alpha = 0.8f))
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
package org.tasks.compose.settings
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Lock
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.tasks.themes.ThemeColor
|
||||
|
||||
@Composable
|
||||
fun ColorPicker(
|
||||
hasPro: Boolean,
|
||||
colors: List<ThemeColor>,
|
||||
onSelected: (ThemeColor) -> Unit,
|
||||
onColorWheelSelected: () -> Unit = {},
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(minSize = 48.dp),
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
item {
|
||||
ColorWheelCircle(
|
||||
onClick = onColorWheelSelected,
|
||||
hasPro = hasPro,
|
||||
)
|
||||
}
|
||||
items(colors) { color ->
|
||||
ColorCircle(
|
||||
color = color,
|
||||
locked = !(hasPro || color.isFree),
|
||||
onClick = { onSelected(color) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColorCircle(
|
||||
color: ThemeColor,
|
||||
locked: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.aspectRatio(1f)
|
||||
.clickable(onClick = onClick)
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(color.primaryColor))
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
shape = CircleShape
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (locked) {
|
||||
LockIcon(tint = Color(color.colorOnPrimary))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColorWheelCircle(
|
||||
onClick: () -> Unit,
|
||||
hasPro: Boolean,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.aspectRatio(1f)
|
||||
.clickable(onClick = onClick)
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
brush = Brush.sweepGradient(
|
||||
colors = listOf(
|
||||
Color.Red,
|
||||
Color.Magenta,
|
||||
Color.Blue,
|
||||
Color.Cyan,
|
||||
Color.Green,
|
||||
Color.Yellow,
|
||||
Color.Red
|
||||
)
|
||||
)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
shape = CircleShape
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (!hasPro) {
|
||||
LockIcon(tint = Color.Black)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LockIcon(tint: Color) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Lock,
|
||||
contentDescription = null,
|
||||
tint = tint,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package org.tasks.compose.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import org.tasks.themes.ThemeColor
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ColorPickerDialog(
|
||||
hasPro: Boolean,
|
||||
colors: List<ThemeColor>,
|
||||
onDismiss: () -> Unit,
|
||||
onColorSelected: (ThemeColor) -> Unit,
|
||||
onColorWheelSelected: () -> Unit = {},
|
||||
) {
|
||||
BasicAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||
) {
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.large,
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.9f)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Box(modifier = Modifier.padding(16.dp)) {
|
||||
ColorPicker(
|
||||
colors = colors,
|
||||
onSelected = { color ->
|
||||
onColorSelected(color)
|
||||
onDismiss()
|
||||
},
|
||||
onColorWheelSelected = {
|
||||
onColorWheelSelected()
|
||||
onDismiss()
|
||||
},
|
||||
hasPro = hasPro,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package org.tasks.jobs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.tasks.analytics.Firebase
|
||||
import org.tasks.caldav.CaldavClientProvider
|
||||
import org.tasks.caldav.property.CalendarIcon
|
||||
import org.tasks.data.dao.CaldavDao
|
||||
import org.tasks.data.entity.CaldavAccount
|
||||
import org.tasks.injection.BaseWorker
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltWorker
|
||||
class UpgradeIconSyncWork @AssistedInject constructor(
|
||||
@Assisted context: Context,
|
||||
@Assisted workerParams: WorkerParameters,
|
||||
firebase: Firebase,
|
||||
private val clientProvider: CaldavClientProvider,
|
||||
private val caldavDao: CaldavDao,
|
||||
) : BaseWorker(context, workerParams, firebase) {
|
||||
override suspend fun run(): Result {
|
||||
var response = Result.success()
|
||||
caldavDao
|
||||
.getAccounts(CaldavAccount.TYPE_TASKS, CaldavAccount.TYPE_CALDAV)
|
||||
.forEach { account ->
|
||||
Timber.d("Uploading icons for $account")
|
||||
val caldavClient = clientProvider.forAccount(account)
|
||||
caldavClient.calendars().forEach { remote ->
|
||||
val url = remote.href
|
||||
val calendar = caldavDao
|
||||
.getCalendarByUrl(account.uuid!!, url.toString())
|
||||
?.takeIf { !it.readOnly() && it.icon?.isNotBlank() == true }
|
||||
?: run {
|
||||
Timber.d("No icon set for $url")
|
||||
return@forEach
|
||||
}
|
||||
val icon = remote[CalendarIcon::class.java]?.icon
|
||||
if (icon?.isNotBlank() == true) {
|
||||
Timber.d("Remote icon already set for $url")
|
||||
return@forEach
|
||||
}
|
||||
Timber.d("Uploading icon to ${calendar.icon} for $url")
|
||||
caldavClient.updateIcon(
|
||||
url = url,
|
||||
icon = calendar.icon,
|
||||
onFailure = { response = Result.retry() }
|
||||
)
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue