mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
475
Commits
v1.3.11
...
v3.0.0-beta1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12f0662146 | ||
|
|
7cc81bd554 | ||
|
|
618fddb698 | ||
|
|
0f74cb9b39 | ||
|
|
743f5aeb89 | ||
|
|
9260a24e0d | ||
|
|
49fc0f018a | ||
|
|
ad7089ea7c | ||
|
|
e74b83581a | ||
|
|
a3fb26f967 | ||
|
|
2b4161b9e7 | ||
|
|
d38f2ea3e9 | ||
|
|
ba4445f19d | ||
|
|
33cab939f0 | ||
|
|
69ce7e18f5 | ||
|
|
e2014a26fc | ||
|
|
1e806ea416 | ||
|
|
6cf85935f9 | ||
|
|
25b838da5c | ||
|
|
03676346e9 | ||
|
|
f2e6d8978c | ||
|
|
fbbbf751c5 | ||
|
|
132b3996fc | ||
|
|
7b41f83a53 | ||
|
|
b6027186c0 | ||
|
|
d2d126e9d9 | ||
|
|
89632d864a | ||
|
|
2cd209ee03 | ||
|
|
b96d6ef4db | ||
|
|
d6af8e21cc | ||
|
|
ae2d1c6868 | ||
|
|
98cf4ff3e7 | ||
|
|
679c9cd070 | ||
|
|
5b281303d9 | ||
|
|
cb28bbfaf3 | ||
|
|
cd190389cd | ||
|
|
0af469af07 | ||
|
|
1689153070 | ||
|
|
520458b107 | ||
|
|
be8f4919c8 | ||
|
|
deb012600d | ||
|
|
15371e01ef | ||
|
|
0e3ee655ef | ||
|
|
da5af8e4b1 | ||
|
|
6c848eaa55 | ||
|
|
15054d836d | ||
|
|
0ac9bba482 | ||
|
|
247bc13e0a | ||
|
|
33126be817 | ||
|
|
b02b31043b | ||
|
|
6786b5aa8a | ||
|
|
8577e7bae8 | ||
|
|
df88399e7a | ||
|
|
16392d5721 | ||
|
|
2edf5411ce | ||
|
|
89e46d3858 | ||
|
|
da45eb5dda | ||
|
|
a1868f495d | ||
|
|
ceb5034c3b | ||
|
|
02ff7089c0 | ||
|
|
2fa33b13a3 | ||
|
|
96a654638a | ||
|
|
b4aa7b0815 | ||
|
|
df5389c27e | ||
|
|
0611ec0e08 | ||
|
|
c325112b44 | ||
|
|
65f9e1d9db | ||
|
|
c97c8f4852 | ||
|
|
6b2073eff2 | ||
|
|
2b08d9a4d0 | ||
|
|
977f135c50 | ||
|
|
3c777d377e | ||
|
|
b98994f2f1 | ||
|
|
85860a53bf | ||
|
|
7a967ce648 | ||
|
|
4c7eac870d | ||
|
|
ff58e01551 | ||
|
|
004ea6cbcb | ||
|
|
a42b88aa78 | ||
|
|
43088cf8b8 | ||
|
|
9460584891 | ||
|
|
570118ca94 | ||
|
|
d9f0de2e05 | ||
|
|
3d5dcfb21d | ||
|
|
e0dc9ea737 | ||
|
|
b4d0904f55 | ||
|
|
2caee59713 | ||
|
|
6712ba78ba | ||
|
|
cba30aee47 | ||
|
|
c6cd8d68f7 | ||
|
|
d2fbf9d79f | ||
|
|
738a35dbe9 | ||
|
|
1278ec55c2 | ||
|
|
95ff34b373 | ||
|
|
1329429130 | ||
|
|
8c3c09dc03 | ||
|
|
10bf65bd09 | ||
|
|
b57b3458ab | ||
|
|
6aeee4816c | ||
|
|
3c8f24fa50 | ||
|
|
5e60629b4c | ||
|
|
21d740ae51 | ||
|
|
f4e8f08f4f | ||
|
|
7bf472023f | ||
|
|
01fc9654f8 | ||
|
|
c1b247c219 | ||
|
|
101fb68205 | ||
|
|
cda16ca06a | ||
|
|
e9b29a2a43 | ||
|
|
5df809a492 | ||
|
|
ffe8a58746 | ||
|
|
eab0296125 | ||
|
|
2ff143b858 | ||
|
|
3b2dcc1a5c | ||
|
|
d59dc8c225 | ||
|
|
877c5d9013 | ||
|
|
11a19da0b8 | ||
|
|
eda81822aa | ||
|
|
3b00613dde | ||
|
|
a434a0bee0 | ||
|
|
33ca43eaf1 | ||
|
|
df4ec3501b | ||
|
|
fc54e540e8 | ||
|
|
e8481cc8cd | ||
|
|
eb8a9b3b46 | ||
|
|
79ec3ba932 | ||
|
|
8f910b6dcc | ||
|
|
92fa181925 | ||
|
|
ca82f96b30 | ||
|
|
2ea2bb1765 | ||
|
|
fb22798469 | ||
|
|
d48f475a34 | ||
|
|
c88c57e856 | ||
|
|
17892fbd24 | ||
|
|
2352974d4d | ||
|
|
e216fbf66e | ||
|
|
f3a947a8a3 | ||
|
|
00264c1d2b | ||
|
|
54a02837fd | ||
|
|
7a5af76ebb | ||
|
|
92bd754623 | ||
|
|
85b9978e05 | ||
|
|
1ebb2319d4 | ||
|
|
f956625d2e | ||
|
|
9c2ebd9d04 | ||
|
|
fb0527b0bf | ||
|
|
6b922e8845 | ||
|
|
663e2d3b08 | ||
|
|
858b86af6a | ||
|
|
647300fd3a | ||
|
|
3eaf99390e | ||
|
|
0ee7915e31 | ||
|
|
10c880a800 | ||
|
|
b1bf658ff3 | ||
|
|
77dc13d9ba | ||
|
|
21b6cfc013 | ||
|
|
5d953ee23e | ||
|
|
42f2f5bfee | ||
|
|
6708901394 | ||
|
|
788038beb7 | ||
|
|
18ac05ed80 | ||
|
|
02f68821d2 | ||
|
|
62886f00f1 | ||
|
|
f2eb45d00b | ||
|
|
2fca9097c2 | ||
|
|
051677e7dd | ||
|
|
fbcd595b45 | ||
|
|
aa38f58f62 | ||
|
|
a3b01a02ee | ||
|
|
8544983984 | ||
|
|
07a11799b1 | ||
|
|
65a7b1933a | ||
|
|
bbfb735124 | ||
|
|
7b474fb0e1 | ||
|
|
c6524d7cee | ||
|
|
148cdf5109 | ||
|
|
60a3039415 | ||
|
|
6f7b6e30c9 | ||
|
|
08c095eae4 | ||
|
|
9087be6282 | ||
|
|
d46fe35eab | ||
|
|
1afb77ee33 | ||
|
|
a9215fc11c | ||
|
|
d80a110280 | ||
|
|
fdfc7f44fb | ||
|
|
26fbfc08f7 | ||
|
|
89049ee3ab | ||
|
|
68f81f900b | ||
|
|
513bd891af | ||
|
|
37cd753ab8 | ||
|
|
34bf2311b4 | ||
|
|
a86093f617 | ||
|
|
71f66e6004 | ||
|
|
2d1ab85e01 | ||
|
|
f029eb9102 | ||
|
|
471837ab88 | ||
|
|
1e80d742cc | ||
|
|
16b6273938 | ||
|
|
3407370563 | ||
|
|
3cda73a36c | ||
|
|
b604c423ad | ||
|
|
0c8b121c2c | ||
|
|
da11961611 | ||
|
|
48b9952fc0 | ||
|
|
99fa99eba5 | ||
|
|
06e67e04ac | ||
|
|
4a2f7c20a2 | ||
|
|
82e8769407 | ||
|
|
ad38702df1 | ||
|
|
3f98c62a8f | ||
|
|
ec7447992f | ||
|
|
99958e13eb | ||
|
|
b9fe5d4a14 | ||
|
|
1443fd5714 | ||
|
|
798083b235 | ||
|
|
18a7311089 | ||
|
|
9f49d62f12 | ||
|
|
0c3c4e7ea1 | ||
|
|
7cdf653842 | ||
|
|
32d99eada8 | ||
|
|
9e4adb16c6 | ||
|
|
4b5638e4f0 | ||
|
|
d8fb9cdd30 | ||
|
|
3920489dcd | ||
|
|
09d013588d | ||
|
|
d8bd03c65d | ||
|
|
8b2c6b9498 | ||
|
|
0f63d3a0d4 | ||
|
|
672eb333b0 | ||
|
|
de609b4d05 | ||
|
|
7e6e9eee36 | ||
|
|
a321bb733c | ||
|
|
490369a3e1 | ||
|
|
a2cce044ef | ||
|
|
8884bb88c7 | ||
|
|
313b0e9131 | ||
|
|
47048cf7e7 | ||
|
|
ef1c4615aa | ||
|
|
c9587b2bf0 | ||
|
|
669fa2fd22 | ||
|
|
fb8ec72825 | ||
|
|
d1687f574f | ||
|
|
72f47bec1f | ||
|
|
20e8aecf83 | ||
|
|
ee41474670 | ||
|
|
ac29e28eda | ||
|
|
58b96cbdee | ||
|
|
0c1fc15251 | ||
|
|
7b8e0cc952 | ||
|
|
c8aa591feb | ||
|
|
afa4d6e807 | ||
|
|
e6b7eda9e7 | ||
|
|
bbc4e05c8f | ||
|
|
50bccbaa2e | ||
|
|
d71e83a2af | ||
|
|
92a67702b7 | ||
|
|
2f9185efa7 | ||
|
|
a10216520b | ||
|
|
d5354e5156 | ||
|
|
a8f9b1f732 | ||
|
|
08a796e621 | ||
|
|
15f3a68f8e | ||
|
|
e0bc6dafa0 | ||
|
|
430c1236d7 | ||
|
|
3ac357161a | ||
|
|
3c5f1bf21f | ||
|
|
5544cd3770 | ||
|
|
71a060feb9 | ||
|
|
25d643257c | ||
|
|
85900e5bff | ||
|
|
d3257d6c5e | ||
|
|
afe1399f72 | ||
|
|
20ce2d3e22 | ||
|
|
cea8f2290d | ||
|
|
1b5b59c41b | ||
|
|
8df7c19e84 | ||
|
|
88b0069b94 | ||
|
|
3b8ddc9f06 | ||
|
|
708c7f38ec | ||
|
|
139daa1cc2 | ||
|
|
b85ac778c0 | ||
|
|
a3e2c9e61d | ||
|
|
ad78a0570c | ||
|
|
7e6ee274d8 | ||
|
|
daf7411c36 | ||
|
|
d3dedbe6cd | ||
|
|
d5411586d5 | ||
|
|
94e2d40554 | ||
|
|
db80fa1b46 | ||
|
|
9b569b8d07 | ||
|
|
90288880fb | ||
|
|
870e518ff6 | ||
|
|
6d89169bae | ||
|
|
ac77ee2703 | ||
|
|
18dcf603e9 | ||
|
|
71aaba1b86 | ||
|
|
64b9d5f17a | ||
|
|
a9f23c4bfe | ||
|
|
f454f46778 | ||
|
|
432936ea2c | ||
|
|
8211b0ac88 | ||
|
|
c314987e13 | ||
|
|
fc6cd74752 | ||
|
|
a47011e579 | ||
|
|
fbeeec6539 | ||
|
|
e1cd79cd9d | ||
|
|
692ced1ee7 | ||
|
|
c5cc127df4 | ||
|
|
5f00f10ae8 | ||
|
|
0fc9716f06 | ||
|
|
48fd3543e3 | ||
|
|
41985794c2 | ||
|
|
87a92fd001 | ||
|
|
f6f53f2a15 | ||
|
|
2531769b79 | ||
|
|
e9fa9f6cb8 | ||
|
|
b98ca86886 | ||
|
|
d917c8ffe6 | ||
|
|
5d44737199 | ||
|
|
6e8693a6b0 | ||
|
|
6d9e7404a1 | ||
|
|
70ceb389b5 | ||
|
|
832e364a2f | ||
|
|
51f9c5c7d8 | ||
|
|
15141c8528 | ||
|
|
0cdac10fd4 | ||
|
|
aaab548bfd | ||
|
|
46611b5606 | ||
|
|
fdbdb46577 | ||
|
|
e7fd81d0eb | ||
|
|
ce50bae6a0 | ||
|
|
8331cf3866 | ||
|
|
670262e53b | ||
|
|
c584f942cf | ||
|
|
e83b6bb583 | ||
|
|
cc5de668f0 | ||
|
|
653c7b8c4b | ||
|
|
4e38bcd4cc | ||
|
|
fcbeda4888 | ||
|
|
4101fddc5b | ||
|
|
86df7bea22 | ||
|
|
4db8c0c286 | ||
|
|
1d03335903 | ||
|
|
b1b541014f | ||
|
|
b40a7e435f | ||
|
|
8305fb4c5f | ||
|
|
6c678be511 | ||
|
|
2823663f09 | ||
|
|
033c2264f8 | ||
|
|
8eb6ce19f7 | ||
|
|
1cfc4e6f65 | ||
|
|
454d76e305 | ||
|
|
f3609fc3a5 | ||
|
|
16da51d095 | ||
|
|
51fb4c7a29 | ||
|
|
0b27cb55dc | ||
|
|
9e836eef69 | ||
|
|
07be4b8258 | ||
|
|
217087c6d4 | ||
|
|
2315a18c86 | ||
|
|
f59e9d2dc5 | ||
|
|
1da220085c | ||
|
|
962b2a01f1 | ||
|
|
eeda14f424 | ||
|
|
6269e20085 | ||
|
|
3d05296eb2 | ||
|
|
e90ab95cd0 | ||
|
|
23ec2f025e | ||
|
|
624898dd5e | ||
|
|
c6d6604399 | ||
|
|
7473704954 | ||
|
|
7a6b216bf6 | ||
|
|
1ef09124af | ||
|
|
6340f73d61 | ||
|
|
b9f4992a33 | ||
|
|
07a1fee013 | ||
|
|
63f37e51ad | ||
|
|
01ea08a5cb | ||
|
|
bddb20c5bb | ||
|
|
f9a97297da | ||
|
|
5fc2b56635 | ||
|
|
172953ccb3 | ||
|
|
d55300f53d | ||
|
|
c3d4500e3a | ||
|
|
cf72e28727 | ||
|
|
b488a1be16 | ||
|
|
805d0cb4b6 | ||
|
|
fa045c2504 | ||
|
|
23fd373a9b | ||
|
|
ab619cb073 | ||
|
|
2d96822014 | ||
|
|
887adc87de | ||
|
|
4219bd02c5 | ||
|
|
48544464ae | ||
|
|
9b4f2477e4 | ||
|
|
26e8840484 | ||
|
|
b6ef6d3ce9 | ||
|
|
cdfcd24049 | ||
|
|
95a8e0b125 | ||
|
|
84b7509f33 | ||
|
|
a53baec9dc | ||
|
|
4b4bc86cf7 | ||
|
|
4d3860089e | ||
|
|
758f55f13c | ||
|
|
1e191c3adc | ||
|
|
1ba2cf7c9d | ||
|
|
9ed1cc00d5 | ||
|
|
3be1173197 | ||
|
|
f6e69c62b1 | ||
|
|
6d9679a7fc | ||
|
|
6ea260a81f | ||
|
|
26da600761 | ||
|
|
33172a7aee | ||
|
|
7eb9f6ac95 | ||
|
|
fc06c285d5 | ||
|
|
ca2e4fec79 | ||
|
|
87ca90f6cc | ||
|
|
7dfea78c87 | ||
|
|
d7808009dc | ||
|
|
62afb7a130 | ||
|
|
87bef9cb9c | ||
|
|
3860cb21c6 | ||
|
|
bf4ad7886c | ||
|
|
1d51c207f4 | ||
|
|
2eb3f70e0c | ||
|
|
eb4d883194 | ||
|
|
3f5c9ff9de | ||
|
|
47039796de | ||
|
|
fd054abcca | ||
|
|
2861bfbd47 | ||
|
|
6ea3127f9c | ||
|
|
f7acb5e115 | ||
|
|
b1ad1f7699 | ||
|
|
ab713df3c0 | ||
|
|
e0d44f2f1c | ||
|
|
61463bc05f | ||
|
|
a219fc3527 | ||
|
|
1cd4028ce8 | ||
|
|
de9a30eb93 | ||
|
|
2210561af3 | ||
|
|
c5ddc474c4 | ||
|
|
688748da36 | ||
|
|
753d311593 | ||
|
|
5bae3d9931 | ||
|
|
a3495a8218 | ||
|
|
5fa68f77a2 | ||
|
|
dbba2e9791 | ||
|
|
6207f26a68 | ||
|
|
7fcd39822e | ||
|
|
d20f8a604f | ||
|
|
a742646127 | ||
|
|
f5078d5ef2 | ||
|
|
3784084abc | ||
|
|
2f463b7735 | ||
|
|
7be5625d66 | ||
|
|
aef14a9cd2 | ||
|
|
67648a04d3 | ||
|
|
58345e3194 | ||
|
|
4cb2ad9a5a | ||
|
|
27395d8e22 | ||
|
|
230c5ea180 | ||
|
|
4d4370ddce | ||
|
|
7e913e7847 | ||
|
|
ec7871e4e9 | ||
|
|
cc5a45a14f | ||
|
|
5fff3457ba | ||
|
|
6f5d66c1d9 | ||
|
|
d94795bf4f | ||
|
|
009b71c42a | ||
|
|
a10716d74e | ||
|
|
b44bf5f401 | ||
|
|
e0af1838e2 | ||
|
|
7d5502a34f | ||
|
|
78ccddf87c | ||
|
|
df1bc48e84 |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"mcp__ide__getDiagnostics",
|
||||
"Bash(./gradlew tasks:*)",
|
||||
"Bash(./gradlew:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(rg:*)",
|
||||
"WebSearch",
|
||||
"WebFetch(domain:support.google.com)",
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:mvnrepository.com)",
|
||||
"WebFetch(domain:kotlinlang.org)",
|
||||
"WebFetch(domain:developer.android.com)",
|
||||
"WebFetch(domain:issuetracker.google.com)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to fix import statements after removing app-common module.
|
||||
This script fixes R class imports from app-common to app module.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def fix_r_import(file_path):
|
||||
"""Fix R import in a single file."""
|
||||
print(f"Fixing R import in: {file_path}")
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace the import statement
|
||||
updated_content = re.sub(
|
||||
r'import eu\.darken\.capod\.common\.R$',
|
||||
'import eu.darken.capod.R',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
|
||||
if updated_content != content:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
print(f" ✓ Updated import in {file_path}")
|
||||
return True
|
||||
else:
|
||||
print(f" - No changes needed in {file_path}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main function to fix all import statements."""
|
||||
project_root = Path.cwd()
|
||||
app_src = project_root / "app" / "src"
|
||||
|
||||
# Find all Kotlin files that import eu.darken.capod.common.R
|
||||
files_to_fix = []
|
||||
for kt_file in app_src.rglob("*.kt"):
|
||||
with open(kt_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
if re.search(r'import eu\.darken\.capod\.common\.R$', content, re.MULTILINE):
|
||||
files_to_fix.append(kt_file)
|
||||
|
||||
print(f"Found {len(files_to_fix)} files with incorrect R imports")
|
||||
|
||||
success_count = 0
|
||||
for file_path in files_to_fix:
|
||||
if fix_r_import(file_path):
|
||||
success_count += 1
|
||||
|
||||
print(f"\nFixed imports in {success_count} files")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to fix fully qualified R references in code.
|
||||
This script finds and replaces eu.darken.capod.common.R with R.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def fix_qualified_r_references(file_path):
|
||||
"""Fix fully qualified R references in a single file."""
|
||||
print(f"Fixing qualified R references in: {file_path}")
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace fully qualified references
|
||||
updated_content = re.sub(
|
||||
r'\beu\.darken\.capod\.common\.R\.',
|
||||
'R.',
|
||||
content
|
||||
)
|
||||
|
||||
if updated_content != content:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
print(f" ✓ Fixed qualified R references in {file_path}")
|
||||
return True
|
||||
else:
|
||||
print(f" - No changes needed in {file_path}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main function to fix all qualified R references."""
|
||||
project_root = Path.cwd()
|
||||
app_src = project_root / "app" / "src"
|
||||
|
||||
# Find all Kotlin files that have qualified R references
|
||||
files_to_fix = []
|
||||
for kt_file in app_src.rglob("*.kt"):
|
||||
with open(kt_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
if re.search(r'\beu\.darken\.capod\.common\.R\.', content):
|
||||
files_to_fix.append(kt_file)
|
||||
|
||||
print(f"Found {len(files_to_fix)} files with qualified R references")
|
||||
|
||||
success_count = 0
|
||||
for file_path in files_to_fix:
|
||||
if fix_qualified_r_references(file_path):
|
||||
success_count += 1
|
||||
|
||||
print(f"\nFixed qualified R references in {success_count} files")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to fix remaining R import issues.
|
||||
This script finds files that use R.* but don't have R imports and adds them.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def needs_r_import(file_path):
|
||||
"""Check if file uses R.* but doesn't import R."""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check if file uses R.something
|
||||
uses_r = re.search(r'\bR\.[a-zA-Z_]', content)
|
||||
if not uses_r:
|
||||
return False
|
||||
|
||||
# Check if file already imports R
|
||||
has_import = re.search(r'import.*\.R$', content, re.MULTILINE)
|
||||
if has_import:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def add_r_import(file_path):
|
||||
"""Add R import to a file."""
|
||||
print(f"Adding R import to: {file_path}")
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Find the package line
|
||||
package_match = re.search(r'^package\s+[^\n]+$', content, re.MULTILINE)
|
||||
if not package_match:
|
||||
print(f" Error: No package declaration found in {file_path}")
|
||||
return False
|
||||
|
||||
# Find existing imports
|
||||
import_pattern = r'^import\s+[^\n]+$'
|
||||
existing_imports = list(re.finditer(import_pattern, content, re.MULTILINE))
|
||||
|
||||
if existing_imports:
|
||||
# Insert after the last import
|
||||
last_import = existing_imports[-1]
|
||||
insert_pos = last_import.end()
|
||||
updated_content = (content[:insert_pos] +
|
||||
"\nimport eu.darken.capod.R" +
|
||||
content[insert_pos:])
|
||||
else:
|
||||
# Insert after package line
|
||||
insert_pos = package_match.end()
|
||||
updated_content = (content[:insert_pos] +
|
||||
"\n\nimport eu.darken.capod.R" +
|
||||
content[insert_pos:])
|
||||
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
|
||||
print(f" ✓ Added R import to {file_path}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Main function to fix all R import issues."""
|
||||
project_root = Path.cwd()
|
||||
app_src = project_root / "app" / "src"
|
||||
|
||||
# Find all Kotlin files that need R import
|
||||
files_to_fix = []
|
||||
for kt_file in app_src.rglob("*.kt"):
|
||||
if needs_r_import(kt_file):
|
||||
files_to_fix.append(kt_file)
|
||||
|
||||
print(f"Found {len(files_to_fix)} files that need R imports")
|
||||
|
||||
success_count = 0
|
||||
for file_path in files_to_fix:
|
||||
if add_r_import(file_path):
|
||||
success_count += 1
|
||||
|
||||
print(f"\nAdded R imports to {success_count} files")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to merge strings.xml files from app-common module to app module.
|
||||
This script merges all string entries from app-common into the corresponding app module files.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
|
||||
def merge_strings_xml(app_common_file, app_file):
|
||||
"""Merge strings from app-common file into app file."""
|
||||
print(f"Processing: {app_common_file.name} -> {app_file.name}")
|
||||
|
||||
# Read the app-common strings.xml content
|
||||
with open(app_common_file, 'r', encoding='utf-8') as f:
|
||||
common_content = f.read()
|
||||
|
||||
# Read the app strings.xml content
|
||||
with open(app_file, 'r', encoding='utf-8') as f:
|
||||
app_content = f.read()
|
||||
|
||||
# Extract string entries from app-common (including comments)
|
||||
# Find everything between <resources> and </resources> tags, excluding the tags themselves
|
||||
common_match = re.search(r'<resources[^>]*>(.*?)</resources>', common_content, re.DOTALL)
|
||||
if not common_match:
|
||||
print(f" Warning: No resources found in {app_common_file}")
|
||||
return False
|
||||
|
||||
common_strings = common_match.group(1).strip()
|
||||
|
||||
if not common_strings:
|
||||
print(f" Warning: No string content found in {app_common_file}")
|
||||
return False
|
||||
|
||||
# Find the insertion point in the app file (before </resources>)
|
||||
app_match = re.search(r'(.*?)(\s*</resources>)', app_content, re.DOTALL)
|
||||
if not app_match:
|
||||
print(f" Error: Invalid XML structure in {app_file}")
|
||||
return False
|
||||
|
||||
# Merge the content
|
||||
before_closing = app_match.group(1)
|
||||
closing_tag = app_match.group(2)
|
||||
|
||||
# Add the common strings with proper spacing
|
||||
merged_content = f"{before_closing}\n\n <!-- Strings from app-common -->\n{common_strings}\n{closing_tag}"
|
||||
|
||||
# Write the merged content back to the app file
|
||||
with open(app_file, 'w', encoding='utf-8') as f:
|
||||
f.write(merged_content)
|
||||
|
||||
print(f" ✓ Merged successfully")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Main function to merge all strings.xml files."""
|
||||
project_root = Path.cwd()
|
||||
app_common_res = project_root / "app-common" / "src" / "main" / "res"
|
||||
app_res = project_root / "app" / "src" / "main" / "res"
|
||||
|
||||
if not app_common_res.exists():
|
||||
print(f"Error: app-common resources directory not found: {app_common_res}")
|
||||
return 1
|
||||
|
||||
if not app_res.exists():
|
||||
print(f"Error: app resources directory not found: {app_res}")
|
||||
return 1
|
||||
|
||||
# Find all strings.xml files in app-common
|
||||
common_strings_files = list(app_common_res.glob("*/strings.xml"))
|
||||
|
||||
if not common_strings_files:
|
||||
print("Error: No strings.xml files found in app-common")
|
||||
return 1
|
||||
|
||||
print(f"Found {len(common_strings_files)} strings.xml files to merge")
|
||||
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
|
||||
for common_file in sorted(common_strings_files):
|
||||
# Determine the corresponding app file
|
||||
locale_dir = common_file.parent.name
|
||||
app_file = app_res / locale_dir / "strings.xml"
|
||||
|
||||
if not app_file.exists():
|
||||
print(f" Error: Corresponding app file does not exist: {app_file}")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
if merge_strings_xml(common_file, app_file):
|
||||
success_count += 1
|
||||
else:
|
||||
error_count += 1
|
||||
|
||||
print(f"\nMerge complete:")
|
||||
print(f" ✓ Successfully merged: {success_count}")
|
||||
print(f" ✗ Errors: {error_count}")
|
||||
|
||||
return 0 if error_count == 0 else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github:
|
||||
- d4rken
|
||||
custom:
|
||||
- "https://www.buymeacoffee.com/tydarken"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: 'Common project setup'
|
||||
description: 'Setup the base project'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 #v4.7.0
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
shell: bash
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Cache Gradle Wrapper
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/wrapper/dists/**/gradle*.zip
|
||||
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-wrapper-
|
||||
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'buildSrc/**/*.kt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-caches-
|
||||
|
||||
- name: Cache Android Global Build-Cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.android/build-cache
|
||||
key: ${{ runner.os }}-android-build-cache-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-android-build-cache-
|
||||
@@ -0,0 +1,14 @@
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- changelog-ignore
|
||||
categories:
|
||||
- title: ":rocket: Enhancements"
|
||||
labels:
|
||||
- enhancement
|
||||
- title: ":lady_beetle: Bug fixes"
|
||||
labels:
|
||||
- bug
|
||||
- title: ":shrug: Other changes"
|
||||
labels:
|
||||
- "*"
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Android CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: gradle
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew assembleDebug
|
||||
- name: Run tests
|
||||
run: ./gradlew testGplayDebugUnitTest testFossDebugUnitTest
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Code tests & eval
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
lint-vital:
|
||||
name: Lint vitals
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [ Foss,Gplay ]
|
||||
variant: [ Beta,Release ]
|
||||
module: [ app ]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Check for fatal lint issues
|
||||
run: ./gradlew lintVital${{ matrix.flavor }}${{ matrix.variant }}
|
||||
|
||||
build-modules:
|
||||
name: Build apps
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [ Foss,Gplay ]
|
||||
variant: [ Debug ]
|
||||
module: [ app ]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Build modules
|
||||
run: ./gradlew ${{ matrix.module }}:assemble${{ matrix.flavor }}${{ matrix.variant }}
|
||||
|
||||
test-modules:
|
||||
name: Unit tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: [ Debug,Beta,Release ]
|
||||
flavor: [ testFoss,testGplay ]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Test modules
|
||||
run: ./gradlew ${{ matrix.flavor }}${{ matrix.variant }}UnitTest
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
- uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 #v4.3.1
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
name: Tagged releases
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release-github:
|
||||
name: Create GitHub release
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-22.04
|
||||
environment: foss-production
|
||||
steps:
|
||||
- name: Decode Keystore
|
||||
env:
|
||||
ENCODED_KEYSTORE: ${{ secrets.SIGNING_KEYSTORE_BASE64 }}
|
||||
run: |
|
||||
TMP_KEYSTORE_DIR_PATH="${RUNNER_TEMP}"/keystore
|
||||
mkdir -p "${TMP_KEYSTORE_DIR_PATH}"
|
||||
TMP_KEYSTORE_FILE_PATH="${TMP_KEYSTORE_DIR_PATH}"/keystore.jks
|
||||
echo $ENCODED_KEYSTORE | base64 -di > "${TMP_KEYSTORE_FILE_PATH}"
|
||||
echo "STORE_PATH=$(echo $TMP_KEYSTORE_FILE_PATH)" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Get the version
|
||||
id: tagger
|
||||
uses: jimschubert/query-tag-action@0b288a5fff630fea2e96d61b99047ed823ca19dc #v2.2
|
||||
with:
|
||||
skip-unshallow: 'true'
|
||||
abbrev: false
|
||||
commit-ish: HEAD
|
||||
|
||||
- name: Assemble beta APK
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
run: ./gradlew assembleFossBeta
|
||||
env:
|
||||
VERSION: ${{ github.ref }}
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Assemble production APK
|
||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
run: ./gradlew assembleFossRelease
|
||||
env:
|
||||
VERSION: ${{ github.ref }}
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Create pre-release
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda #v2.2.1
|
||||
with:
|
||||
prerelease: true
|
||||
tag_name: ${{ steps.tagger.outputs.tag }}
|
||||
name: ${{ steps.tagger.outputs.tag }}
|
||||
generate_release_notes: true
|
||||
files: app/build/outputs/apk/foss/beta/*.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create release
|
||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda #v2.2.1
|
||||
with:
|
||||
prerelease: false
|
||||
tag_name: ${{ steps.tagger.outputs.tag }}
|
||||
name: ${{ steps.tagger.outputs.tag }}
|
||||
generate_release_notes: true
|
||||
files: app/build/outputs/apk/foss/release/*.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-gplay:
|
||||
name: Create Google Play release
|
||||
runs-on: ubuntu-22.04
|
||||
environment: gplay-production
|
||||
steps:
|
||||
- name: Decode Keystore
|
||||
env:
|
||||
ENCODED_KEYSTORE: ${{ secrets.SIGNING_KEYSTORE_BASE64 }}
|
||||
run: |
|
||||
TMP_KEYSTORE_DIR_PATH="${RUNNER_TEMP}"/keystore
|
||||
mkdir -p "${TMP_KEYSTORE_DIR_PATH}"
|
||||
TMP_KEYSTORE_FILE_PATH="${TMP_KEYSTORE_DIR_PATH}"/keystore.jks
|
||||
echo $ENCODED_KEYSTORE | base64 -di > "${TMP_KEYSTORE_FILE_PATH}"
|
||||
echo "STORE_PATH=$(echo $TMP_KEYSTORE_FILE_PATH)" >> $GITHUB_ENV
|
||||
|
||||
- name: Decode Google Play service account key
|
||||
env:
|
||||
ENCODED_SERVICE_KEY: ${{ secrets.GPLAY_SERVICE_ACCOUNT_KEY_JSON_BASE64 }}
|
||||
run: |
|
||||
TMP_SERVICEKEY_DIR="${RUNNER_TEMP}"/gplaykey
|
||||
mkdir -p "${TMP_SERVICEKEY_DIR}"
|
||||
TMP_SERVICEKEY_FILE_PATH="${TMP_SERVICEKEY_DIR}"/service_account.json
|
||||
echo $ENCODED_SERVICE_KEY | base64 -di > "${TMP_SERVICEKEY_FILE_PATH}"
|
||||
echo "SUPPLY_JSON_KEY=$(echo $TMP_SERVICEKEY_FILE_PATH)" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Get the version
|
||||
id: tagger
|
||||
uses: jimschubert/query-tag-action@0b288a5fff630fea2e96d61b99047ed823ca19dc #v2.2
|
||||
with:
|
||||
skip-unshallow: 'true'
|
||||
abbrev: false
|
||||
commit-ish: HEAD
|
||||
|
||||
- name: Set up ruby env
|
||||
uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 #v1.229.0
|
||||
with:
|
||||
ruby-version: 3.3.6
|
||||
bundler-cache: true
|
||||
|
||||
- name: Assemble beta and upload to Google Play
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
run: bundle exec fastlane beta
|
||||
env:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Assemble production and upload to Google Play
|
||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
run: bundle exec fastlane production
|
||||
env:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
+15
-2
@@ -1,4 +1,17 @@
|
||||
.idea
|
||||
*.iml
|
||||
.gradle
|
||||
build/
|
||||
/local.properties
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
/.idea/**/*
|
||||
!/.idea/codeStyles/
|
||||
!/.idea/codeStyles/**/*
|
||||
*.jks
|
||||
.local/*
|
||||
/fastlane/report.xml
|
||||
/fastlane/Appfile
|
||||
/fastlane/README.md
|
||||
.kotlin
|
||||
Generated
+168
@@ -0,0 +1,168 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="RIGHT_MARGIN" value="120" />
|
||||
<JSON>
|
||||
<option name="OBJECT_WRAPPING" value="1" />
|
||||
<option name="ARRAY_WRAPPING" value="1" />
|
||||
</JSON>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="false" module="true" />
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" 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="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="1" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JSON">
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</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="METHOD_ANNOTATION_WRAP" value="1" />
|
||||
<option name="CLASS_ANNOTATION_WRAP" value="1" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="1" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
layout: plain
|
||||
permalink: /changelog
|
||||
title: "Changelog"
|
||||
---
|
||||
|
||||
# Changelog for CAPod
|
||||
|
||||
{% for release in site.github.releases %}
|
||||
|
||||
## {{ release.tag_name }} - {{ release.published_at | date: "%B %d, %Y" }}
|
||||
|
||||
{% assign clean_body = release.body | strip %}
|
||||
{% assign no_comments = clean_body | replace: "<!-- Release notes generated using configuration in .github/release.yml", "" %}
|
||||
{% assign no_comments = no_comments | split: "-->" %}
|
||||
{% if no_comments.size > 1 %}
|
||||
{% assign clean_content = no_comments[1] | strip %}
|
||||
{% else %}
|
||||
{% assign clean_content = no_comments[0] | strip %}
|
||||
{% endif %}
|
||||
|
||||
{% comment %} Make links clickable {% endcomment %}
|
||||
{% assign lines = clean_content | split: "
|
||||
" %}
|
||||
{% assign processed_lines = "" %}
|
||||
{% for line in lines %}
|
||||
{% if line contains "**Full Changelog**:" %}
|
||||
{% comment %} Handle Full Changelog links {% endcomment %}
|
||||
{% assign parts = line | split: ": " %}
|
||||
{% if parts.size > 1 %}
|
||||
{% assign url = parts[1] | strip %}
|
||||
{% assign clickable_line = "**[View Changes](" | append: url | append: ")**" %}
|
||||
{% assign processed_lines = processed_lines | append: clickable_line | append: "
|
||||
" %}
|
||||
{% else %}
|
||||
{% assign processed_lines = processed_lines | append: line | append: "
|
||||
" %}
|
||||
{% endif %}
|
||||
{% elsif line contains " in https://github.com/" and line contains "/pull/" %}
|
||||
{% comment %} Handle pull request links {% endcomment %}
|
||||
{% assign pr_parts = line | split: " in https://github.com/" %}
|
||||
{% if pr_parts.size > 1 %}
|
||||
{% assign before_url = pr_parts[0] %}
|
||||
{% assign after_url = pr_parts[1] %}
|
||||
{% assign url = "https://github.com/" | append: after_url %}
|
||||
{% assign pr_number = after_url | split: "/pull/" %}
|
||||
{% if pr_number.size > 1 %}
|
||||
{% assign pr_num = pr_number[1] | split: " " | first %}
|
||||
{% assign clickable_line = before_url | append: " in [#" | append: pr_num | append: "](" | append: url | append: ")" %}
|
||||
{% assign processed_lines = processed_lines | append: clickable_line | append: "
|
||||
" %}
|
||||
{% else %}
|
||||
{% assign processed_lines = processed_lines | append: line | append: "
|
||||
" %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% assign processed_lines = processed_lines | append: line | append: "
|
||||
" %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% assign processed_lines = processed_lines | append: line | append: "
|
||||
" %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% comment %} Add proper spacing between sections and bullet points {% endcomment %}
|
||||
{% assign final_content = processed_lines | replace: "
|
||||
### ", "
|
||||
|
||||
### " %}
|
||||
{% assign final_content = final_content | replace: "
|
||||
## ", "
|
||||
|
||||
## " %}
|
||||
{% assign final_content = final_content | replace: "
|
||||
- ", "
|
||||
|
||||
- " %}
|
||||
|
||||
{% comment %} Check if there are any bullet points (actual release notes) {% endcomment %}
|
||||
{% if final_content contains "## " or final_content contains "- " %}
|
||||
{{ final_content | markdownify }}
|
||||
{% else %}
|
||||
*No release notes available.*
|
||||
|
||||
{{ final_content | markdownify }}
|
||||
{% endif %}
|
||||
|
||||
---
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,158 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Commands
|
||||
|
||||
### Build Commands
|
||||
|
||||
```bash
|
||||
# Build debug version
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Build all variants (FOSS and Google Play flavors)
|
||||
./gradlew assemble
|
||||
|
||||
# Build specific flavor and type
|
||||
./gradlew assembleFossDebug
|
||||
./gradlew assembleGplayRelease
|
||||
|
||||
# Build app bundles for Play Store
|
||||
./gradlew bundleGplayRelease
|
||||
```
|
||||
|
||||
### Testing Commands
|
||||
|
||||
```bash
|
||||
# Run all unit tests
|
||||
./gradlew test
|
||||
|
||||
# Run unit tests for specific variant
|
||||
./gradlew testFossDebugUnitTest
|
||||
|
||||
# Run instrumentation tests (requires connected device/emulator)
|
||||
./gradlew connectedAndroidTest
|
||||
./gradlew connectedFossDebugAndroidTest
|
||||
|
||||
# Run all checks (lint + tests)
|
||||
./gradlew check
|
||||
```
|
||||
|
||||
### Code Quality Commands
|
||||
|
||||
```bash
|
||||
# Run lint for all variants
|
||||
./gradlew lint
|
||||
|
||||
# Run lint for specific variant
|
||||
./gradlew lintFossDebug
|
||||
|
||||
# Auto-fix lint issues where possible
|
||||
./gradlew lintFix
|
||||
|
||||
# Update lint baseline
|
||||
./gradlew updateLintBaseline
|
||||
```
|
||||
|
||||
### Release Commands
|
||||
|
||||
```bash
|
||||
./gradlew assembleFossRelease assembleGplayRelease
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Multi-Module Structure
|
||||
|
||||
- **app/**: Main Android application with FOSS and Google Play flavors
|
||||
- **app-common/**: Shared code between main app
|
||||
|
||||
### Core Architecture Patterns
|
||||
|
||||
- **MVVM**: ViewModels with LiveData/StateFlow for UI state management
|
||||
- **Dependency Injection**: Hilt/Dagger for dependency management
|
||||
- **Coroutines**: Extensive use of Kotlin coroutines for async operations
|
||||
- **Repository Pattern**: Data layer abstraction for monitoring and settings
|
||||
|
||||
### Key Components
|
||||
|
||||
#### PodMonitor System
|
||||
|
||||
- `PodMonitor`: Core service that detects and tracks AirPods via Bluetooth LE
|
||||
- `MonitorControl`: Manages background monitoring worker lifecycle
|
||||
- `MonitorWorker`: Background worker that continuously scans for AirPods
|
||||
- `BluetoothEventReceiver`: Handles system Bluetooth events
|
||||
|
||||
#### Reaction System
|
||||
|
||||
- `ReactionSettingsFragment`: Configuration for popup notifications
|
||||
- `PopUpWindow`: Displays AirPods status when case is opened
|
||||
- `PopUpPodViewFactory`: Creates UI components for different pod models
|
||||
|
||||
#### Common Utilities
|
||||
|
||||
- `EdgeToEdgeHelper`: Handles Android edge-to-edge display insets
|
||||
|
||||
### Build Configuration
|
||||
|
||||
#### Flavors
|
||||
|
||||
- **FOSS**: Open-source version without Google Play dependencies
|
||||
- **Google Play**: Version with billing client for in-app purchases
|
||||
|
||||
#### Build Types
|
||||
|
||||
- **debug**: Unobfuscated, full logging, no minification
|
||||
- **beta**: Obfuscated, production-ready with strict lint checks
|
||||
- **release**: Fully optimized for production distribution
|
||||
|
||||
### Data Flow Architecture
|
||||
|
||||
The app follows a unidirectional data flow:
|
||||
|
||||
1. `BluetoothEventReceiver` detects Bluetooth events
|
||||
2. `MonitorWorker` scans for AirPods beacon data
|
||||
3. `PodMonitor` processes and stores device information
|
||||
4. ViewModels observe monitor data via repositories
|
||||
5. UI components react to ViewModel state changes
|
||||
6. `ReactionSystem` triggers popups and notifications
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
- **Unit Tests**: Located in `app-common/src/test/` for shared logic
|
||||
- **Test Flavors**: Separate test configurations for FOSS and Google Play variants
|
||||
|
||||
### Key Dependencies
|
||||
|
||||
- **Hilt**: Dependency injection framework
|
||||
- **AndroidX Navigation**: Fragment navigation with SafeArgs
|
||||
- **WorkManager**: Background task scheduling for monitoring
|
||||
- **Moshi**: JSON serialization for configuration and debugging
|
||||
- **Material Design**: UI components following Material Design guidelines
|
||||
|
||||
## Development Notes
|
||||
|
||||
### Bluetooth LE Implementation
|
||||
|
||||
The app uses Android's Bluetooth LE APIs to scan for Apple device advertisements. The core scanning logic is in
|
||||
`MonitorWorker` which runs as a long-lived background task.
|
||||
|
||||
### Multi-Platform Considerations
|
||||
|
||||
Code shared between phone and Wear OS apps is placed in `app-common`. When modifying shared functionality, ensure
|
||||
compatibility across both platforms.
|
||||
|
||||
### Localization Guidelines
|
||||
|
||||
When adding new user-facing strings:
|
||||
|
||||
- **Always use string resources**: Never hardcode user-facing text in layouts or code
|
||||
- **Follow naming conventions**: Use descriptive, hierarchical naming (e.g., `profiles_name_default`, `settings_bluetooth_enabled`)
|
||||
- **Provide context**: String names should indicate usage and location
|
||||
- **Consider pluralization**: Use Android plural resources (`<plurals>`) when quantities vary
|
||||
|
||||
Examples of correct string naming:
|
||||
- `profiles_create_title` (screen title)
|
||||
- `profiles_name_label` (form field label)
|
||||
- `profiles_delete_confirmation` (dialog message)
|
||||
- `error_network_unavailable` (error message)
|
||||
+103
-92
@@ -1,43 +1,48 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.5)
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
artifactory (3.0.15)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.554.0)
|
||||
aws-sdk-core (3.126.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.54.0)
|
||||
aws-sdk-core (~> 3, >= 3.126.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.112.0)
|
||||
aws-sdk-core (~> 3, >= 3.126.0)
|
||||
aws-eventstream (1.3.2)
|
||||
aws-partitions (1.1109.0)
|
||||
aws-sdk-core (3.224.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.101.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.188.0)
|
||||
aws-sdk-core (~> 3, >= 3.224.1)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.4.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.3.0)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.4)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.91.0)
|
||||
faraday (1.9.3)
|
||||
excon (0.109.0)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -53,27 +58,27 @@ GEM
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-multipart (1.1.1)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.204.3)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.228.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
@@ -82,33 +87,38 @@ GEM
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (~> 2.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
xcpretty (~> 0.4.1)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.16.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-core (0.4.2)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@@ -116,99 +126,100 @@ GEM
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.10.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.7.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.11.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.29.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.6.1)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.36.1)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.45.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.29.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.1.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.4)
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.5.0)
|
||||
json (2.6.1)
|
||||
jwt (2.3.0)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.7.6)
|
||||
jwt (2.10.1)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
optparse (0.1.1)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (4.0.6)
|
||||
rake (13.0.6)
|
||||
representable (3.1.1)
|
||||
plist (3.7.2)
|
||||
public_suffix (5.1.1)
|
||||
rake (13.3.0)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.5)
|
||||
rouge (2.0.7)
|
||||
rexml (3.4.1)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.16.0)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.18.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
unf (0.2.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.21.0)
|
||||
xcodeproj (1.27.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.4.1)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
universal-darwin-24
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
|
||||
+55
-11
@@ -1,24 +1,68 @@
|
||||
# Privacy policy for the app `CAPod`
|
||||
---
|
||||
layout: plain
|
||||
permalink: /privacy
|
||||
title: "Privacy Policy"
|
||||
---
|
||||
|
||||
* I do not collect personal information
|
||||
* I do not sell, monetize or otherwise misappropriate any collected data.
|
||||
# Privacy policy
|
||||
This is the privacy policy for the Android app "CAPod - Companion for AirPods" by Matthias Urhahn (darken).
|
||||
|
||||
Anonymous device information may be collected in the event of a crash (see [Automatic crash reports](#automatic-crash-reports)).
|
||||
## Preamble
|
||||
CAPod respects your privacy.
|
||||
|
||||
I do not collect, share or sell personal information.
|
||||
|
||||
Send a [quick mail](mailto:support@darken.eu) if you have questions.
|
||||
|
||||
My underlying privacy principle is the [Golden Rule](https://en.wikipedia.org/wiki/Golden_Rule).
|
||||
|
||||
Send a [quick mail](mailto:support@darken.eu) if you have further questions.
|
||||
## Location data
|
||||
|
||||
## Automatic crash reports
|
||||
CAPod does not collect, share or sell location data.
|
||||
|
||||
The app uses "Bugsnag" for automatic crash reports:
|
||||
Location permissions are required to receive Bluetooth Low Energy (BLE) data and ebale its core functionality.
|
||||
The permission "access fine location" (`ACCESS_FINE_LOCATION`) and "access coarse location" (`ACCESS_COARSE_LOCATION`) are required to receive Bluetooth Low Energy data on Android 11 and lower.
|
||||
On Android 12+ the newer and more fine grained `BLUETOOTH_SCAN` permission is used instead.
|
||||
|
||||
Bluetooth Low Energy is a technology that devices like AirPods use to communicate their status to nearby devices.
|
||||
CAPod requests location permissions because these permissions are required to work with Bluetooth Low Energy data.
|
||||
This is a privacy measure on Android's side because you could determine someones location by scanning for Bluetooth
|
||||
devices:
|
||||
If you know the physical location of a Bluetooth device (e.g. AirTags) you could use Bluetooth data to calculate your
|
||||
position.
|
||||
|
||||
### Location access in the background
|
||||
|
||||
CAPod uses the "location access in the background" permission (`ACCESS_BACKGROUND_LOCATION`) on Android 11 and older to
|
||||
receive Bluetooth Low Energy data while the app is in the background. This permission enables the "Show popup" and "
|
||||
Autoconnect" features and allows CAPod to react to nearby devices whil the app is closed.
|
||||
|
||||
## Automatic error reports
|
||||
|
||||
*This was removed in v2.11.0+*
|
||||
|
||||
If an error occurs, an automated report may be sent to help me fix the issue.
|
||||
|
||||
This is optional and you can opt out of this in the settings.
|
||||
|
||||
Error reports are collected using "Bugsnag":
|
||||
https://www.bugsnag.com/
|
||||
|
||||
Bugsnags privacy policy can be found here:
|
||||
|
||||
Bugsnag's privacy policy can be found here:
|
||||
https://docs.bugsnag.com/legal/privacy-policy/
|
||||
|
||||
Crash reports may contain device and app related information.
|
||||
Error reports contain device and app information related to the error that occured.
|
||||
Additional information about the error context may also be included, e.g. what this app did shortly before the error.
|
||||
|
||||
You can disable automatic crash reports in the app's settings.
|
||||
Additional details about the type of data that is collected by the error tracking SDK can be found here:
|
||||
https://docs.bugsnag.com/platforms/android/playstore-privacy/
|
||||
|
||||
Error reports are pseudonymous. Unless you tell me your install-ID, I don't know that an error report came from you.
|
||||
|
||||
Error reports are automatically deleted after 90 days.
|
||||
|
||||
## Debug logs
|
||||
|
||||
The app has a debug log feature that can be used to assist troubleshooting efforts. This feature creates a log file that contains verbose output of what the app is doing.
|
||||
|
||||
It is manually triggered by the user through an option in the app settings. The recorded log file can be shared through compatible apps (e.g. your email app) using the system's share dialog. As this log file may contain sensitive information (e.g. details about files or installed applications) it should only be shared with trusted parties.
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<img src="https://github.com/d4rken/capod/raw/main/.assets/banner.png" width="400">
|
||||
<img src="https://github.com/d4rken-org/capod/raw/main/.assets/banner.png" width="400">
|
||||
|
||||
# Companion App for AirPods (CAPod)
|
||||
|
||||

|
||||
[](https://github.com/d4rken/capod/releases/latest)
|
||||
[](https://github.com/d4rken-org/capod/releases/latest)
|
||||
[](https://github.com/d4rken/capod/actions/workflows/code-checks.yml)
|
||||
[](https://crowdin.com/project/capod)
|
||||
[](https://github.com/d4rken-org/capod/edit/main/README.md#download)
|
||||
[](https://github.com/d4rken-org/capod/edit/main/README.md#download)
|
||||
|
||||
A companion app that adds support for AirPod specific features to Android:
|
||||
|
||||
@@ -14,41 +17,61 @@ A companion app that adds support for AirPod specific features to Android:
|
||||
* Ear detection with automatic play/pause.
|
||||
* Automatically connect phone & AirPods.
|
||||
* Show popup when case is opened.
|
||||
* Widgets
|
||||
|
||||
CAPod is ad-free. Some additional features require an in-app purchase.
|
||||
|
||||
Currently supported models:
|
||||
|
||||
* AirPods Gen1
|
||||
* AirPods Gen2
|
||||
* AirPods Pro
|
||||
* AirPods 1. Generation
|
||||
* AirPods 2. Generation
|
||||
* AirPods 3. Generation
|
||||
* AirPods 4. Generation
|
||||
* AirPods 4. Generation with ANC
|
||||
* AirPods Pro 1. Generation
|
||||
* AirPods Pro 2. Generation
|
||||
* AirPods Pro 2. Generation (USB-C)
|
||||
* AirPods Pro 3. Generation
|
||||
* AirPods Max
|
||||
* Power Beats Pro
|
||||
* Power Beats 3
|
||||
* Power Beats 4
|
||||
* Beats Solo 3
|
||||
* Beats Studio 3
|
||||
* Beats X
|
||||
* Beats Flex
|
||||
* Beats Fit Pro
|
||||
* Fake AirPods 1. Generation 🎭
|
||||
* Fake AirPods 2. Generation 🎭
|
||||
* Fake AirPods 3. Generation 🎭
|
||||
* Fake AirPods Pro1 🎭
|
||||
* Fake AirPods Pro2 🎭
|
||||
|
||||
## Download
|
||||
|
||||
* [Google Play](https://play.google.com/store/apps/details?id=eu.darken.capod)
|
||||
* [GitHub](https://github.com/d4rken/capod/releases/latest)
|
||||
| Source | Status |
|
||||
|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [Google Play](https://play.google.com/store/apps/details?id=eu.darken.capod) | [](https://play.google.com/store/apps/details?id=eu.darken.capod) |
|
||||
| [Google Play Beta](https://play.google.com/apps/testing/eu.darken.capod) | [](https://play.google.com/apps/testing/eu.darken.capod) | |
|
||||
| [Github Releases](https://github.com/d4rken-org/capod/releases) | [](https://github.com/d4rken-org/capod/releases/latest) |
|
||||
| [F-Droid](https://f-droid.org/en/packages/eu.darken.capod/) | [](https://f-droid.org/en/packages/eu.darken.capod/) |
|
||||
| [F-Droid (IzzyOnDroid)](https://apt.izzysoft.de/packages/eu.darken.capod/) | [](https://apt.izzysoft.de/packages/eu.darken.capod/) |
|
||||
|
||||
## Support the project
|
||||
|
||||
* Buy the CAPod Pro In-App purchase on [Google Play](https://play.google.com/store/apps/details?id=eu.darken.capod)
|
||||
* [Sponsor development](https://github.com/sponsors/d4rken) on GitHub
|
||||
* Help translate CAPod [on Crowdin](https://crowdin.com/project/capod)
|
||||
* [Buy me a coffee](https://www.buymeacoffee.com/tydarken)
|
||||
|
||||
## Get help
|
||||
|
||||
* [Github Issues](https://github.com/d4rken/capod/issues)
|
||||
* [Github Issues](https://github.com/d4rken-org/capod/issues)
|
||||
* [Discord](https://discord.gg/vHubYPp)
|
||||
|
||||
## Screenshots
|
||||
|
||||
<img src="https://github.com/d4rken/capod/raw/main/.assets/screenshots/1.png" width="200"><img src="https://github.com/d4rken/capod/raw/main/.assets/screenshots/2.png" width="200"><img src="https://github.com/d4rken/capod/raw/main/.assets/screenshots/3.png" width="200"><img src="https://github.com/d4rken/capod/raw/main/.assets/screenshots/4.png" width="200">
|
||||
<img src="https://github.com/d4rken-org/capod/raw/main/.assets/screenshots/1.png" width="200"><img src="https://github.com/d4rken-org/capod/raw/main/.assets/screenshots/2.png" width="200"><img src="https://github.com/d4rken-org/capod/raw/main/.assets/screenshots/3.png" width="200"><img src="https://github.com/d4rken-org/capod/raw/main/.assets/screenshots/4.png" width="200">
|
||||
<img src="https://raw.githubusercontent.com/d4rken-org/capod/main/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png" width="200">
|
||||
|
||||
## Thanks to
|
||||
|
||||
@@ -58,6 +81,10 @@ Currently supported models:
|
||||
Travis & Rye, Erik & Sipes, Brandon & Teplov, Sam. (2019). Handoff All Your Privacy – A Review of Apple’s Bluetooth
|
||||
Low Energy Continuity Protocol. Proceedings on Privacy Enhancing Technologies. 2019. 34-53. 10.2478/popets-2019-0057.
|
||||
* Celosia, Guillaume. (2020). Privacy Challenges in Wireless Communications of the Internet of Things.
|
||||
* The [MagicPods](https://github.com/steam3d/MagicPods-Windows) project. If you are looking for "CAPod for Windows",
|
||||
check it out.
|
||||
* [@kavishdevar](https://github.com/kavishdevar/librepods) and
|
||||
his [LibrePods project](https://github.com/kavishdevar/librepods) for sharing a lot of cool stuff.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
+29
@@ -0,0 +1,29 @@
|
||||
theme: minima
|
||||
plugins:
|
||||
- jekyll-relative-links
|
||||
- jekyll-github-metadata
|
||||
- jemoji
|
||||
|
||||
relative_links:
|
||||
enabled: true
|
||||
collections: true
|
||||
|
||||
title: "CAPod"
|
||||
description: "A companion app for AirPods on Android."
|
||||
author: "by Matthias Urhahn"
|
||||
|
||||
include:
|
||||
- PRIVACY_POLICY.md
|
||||
- README.md
|
||||
- CHANGELOG.md
|
||||
exclude:
|
||||
- buildSrc
|
||||
- gradle/wrapper
|
||||
- fastlane
|
||||
- Gemfile
|
||||
- Gemfile.lock
|
||||
- crowdin*
|
||||
- app
|
||||
- app-common
|
||||
- CONTRIBUTING.md
|
||||
- CLAUDE.md
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ page.lang | default: site.lang | default: 'en' }}">
|
||||
|
||||
{%- include head.html -%}
|
||||
|
||||
<body>
|
||||
|
||||
<main class="page-content" aria-label="Content">
|
||||
<div class="wrapper">
|
||||
{{ content }}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{%- include footer.html -%}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,266 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-kapt'
|
||||
id 'kotlin-parcelize'
|
||||
id 'androidx.navigation.safeargs.kotlin'
|
||||
id 'com.bugsnag.android.gradle'
|
||||
id 'dagger.hilt.android.plugin'
|
||||
}
|
||||
|
||||
def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()
|
||||
def buildTime = new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT+1"))
|
||||
|
||||
android {
|
||||
def packageName = "eu.darken.capod"
|
||||
|
||||
compileSdkVersion buildConfig.compileSdk
|
||||
|
||||
Properties bugsnagProps = new Properties()
|
||||
def bugsnagPropsFile = new File(System.properties['user.home'], ".appconfig/${packageName}/bugsnag.properties")
|
||||
if (bugsnagPropsFile.canRead()) bugsnagProps.load(new FileInputStream(bugsnagPropsFile))
|
||||
|
||||
defaultConfig {
|
||||
applicationId "${packageName}"
|
||||
|
||||
minSdkVersion buildConfig.minSdk
|
||||
targetSdkVersion buildConfig.targetSdk
|
||||
|
||||
versionCode buildConfig.version.code
|
||||
versionName buildConfig.version.name
|
||||
|
||||
testInstrumentationRunner "eu.darken.capod.HiltTestRunner"
|
||||
|
||||
buildConfigField "String", "GITSHA", "\"${gitSha}\""
|
||||
buildConfigField "String", "BUILDTIME", "\"${buildTime}\""
|
||||
|
||||
manifestPlaceholders = [bugsnagApiKey: "fake"]
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {}
|
||||
}
|
||||
def signingPropFile = new File(System.properties['user.home'], ".appconfig/${packageName}/signing.properties")
|
||||
if (signingPropFile.canRead()) {
|
||||
Properties signingProps = new Properties()
|
||||
signingProps.load(new FileInputStream(signingPropFile))
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile new File(signingProps['release.storePath'])
|
||||
keyAlias signingProps['release.keyAlias']
|
||||
storePassword signingProps['release.storePassword']
|
||||
keyPassword signingProps['release.keyPassword']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
def proguardRulesRelease = fileTree(dir: "../proguard", include: ["*.pro"]).asList().toArray()
|
||||
debug {
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
|
||||
proguardFiles proguardRulesRelease
|
||||
proguardFiles 'proguard-rules-debug.pro'
|
||||
manifestPlaceholders = [bugsnagApiKey: bugsnagProps.getProperty("bugsnag.apikey", "")]
|
||||
}
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
lintOptions {
|
||||
abortOnError true
|
||||
fatal 'StopShip'
|
||||
}
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
|
||||
proguardFiles proguardRulesRelease
|
||||
manifestPlaceholders = [bugsnagApiKey: bugsnagProps.getProperty("bugsnag.apikey", "")]
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "version"
|
||||
productFlavors {
|
||||
gplay {
|
||||
|
||||
}
|
||||
foss {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
def flavor = variant.productFlavors[0].name.toUpperCase()
|
||||
def buildType = variant.buildType.name.toUpperCase()
|
||||
def versionCode = defaultConfig.versionCode
|
||||
def versionName = defaultConfig.versionName
|
||||
|
||||
if (variant.buildType.name == "debug") {
|
||||
variant.mergedFlavor.resourceConfigurations.clear()
|
||||
variant.mergedFlavor.resourceConfigurations.add("en")
|
||||
variant.mergedFlavor.resourceConfigurations.add("de")
|
||||
} else if (variant.buildType.name != "debug") {
|
||||
variant.outputs.each { output ->
|
||||
output.outputFileName = "${packageName}-v${versionName}(${versionCode})-${gitSha}-${flavor}-${buildType}.apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
|
||||
freeCompilerArgs += [
|
||||
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-Xuse-experimental=kotlinx.coroutines.FlowPreview",
|
||||
"-Xuse-experimental=kotlin.time.ExperimentalTime",
|
||||
"-Xuse-experimental=kotlin.ExperimentalUnsignedTypes",
|
||||
"-Xuse-experimental=kotlin.contracts.ExperimentalContracts",
|
||||
"-Xopt-in=kotlin.RequiresOptIn"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
test {
|
||||
java.srcDirs += "$projectDir/src/testShared/java"
|
||||
}
|
||||
androidTest {
|
||||
java.srcDirs += "$projectDir/src/testShared/java"
|
||||
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Kotlin
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin.core}"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.kotlin.coroutines}"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.kotlin.coroutines}"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-reflect:${versions.kotlin.core}"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:${versions.kotlin.coroutines}"
|
||||
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${versions.kotlin.coroutines}") {
|
||||
// conflicts with mockito due to direct inclusion of byte buddy
|
||||
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
|
||||
}
|
||||
|
||||
// Debugging
|
||||
implementation('com.bugsnag:bugsnag-android:5.9.2')
|
||||
implementation 'com.getkeepsafe.relinker:relinker:1.4.3'
|
||||
|
||||
implementation("com.squareup.moshi:moshi:1.13.0")
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.13.0")
|
||||
|
||||
// DI
|
||||
implementation "com.google.dagger:dagger:${versions.dagger.core}"
|
||||
implementation "com.google.dagger:dagger-android:${versions.dagger.core}"
|
||||
|
||||
kapt "com.google.dagger:dagger-compiler:${versions.dagger.core}"
|
||||
kapt "com.google.dagger:dagger-android-processor:${versions.dagger.core}"
|
||||
|
||||
implementation "com.google.dagger:hilt-android:${versions.dagger.core}"
|
||||
kapt "com.google.dagger:hilt-android-compiler:${versions.dagger.core}"
|
||||
|
||||
testImplementation "com.google.dagger:hilt-android-testing:${versions.dagger.core}"
|
||||
kaptTest "com.google.dagger:hilt-android-compiler:${versions.dagger.core}"
|
||||
|
||||
androidTestImplementation "com.google.dagger:hilt-android-testing:${versions.dagger.core}"
|
||||
kaptAndroidTest "com.google.dagger:hilt-android-compiler:${versions.dagger.core}"
|
||||
|
||||
kapt "androidx.hilt:hilt-compiler:1.0.0"
|
||||
implementation 'androidx.hilt:hilt-common:1.0.0'
|
||||
kaptTest "androidx.hilt:hilt-compiler:1.0.0"
|
||||
testImplementation 'androidx.hilt:hilt-common:1.0.0'
|
||||
|
||||
// Support libs
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.0'
|
||||
implementation 'androidx.annotation:annotation:1.3.0'
|
||||
|
||||
implementation 'androidx.activity:activity-ktx:1.4.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.4.0'
|
||||
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
|
||||
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:2.3.5"
|
||||
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
|
||||
|
||||
|
||||
def work_version = "2.7.1"
|
||||
implementation "androidx.work:work-runtime:${work_version}"
|
||||
testImplementation "androidx.work:work-testing:${work_version}"
|
||||
implementation "androidx.work:work-runtime-ktx:${work_version}"
|
||||
implementation 'androidx.hilt:hilt-work:1.0.0'
|
||||
|
||||
// IAP
|
||||
gplayImplementation 'com.android.billingclient:billing:4.0.0'
|
||||
|
||||
// UI
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
|
||||
implementation 'com.google.android.material:material:1.6.0-alpha01'
|
||||
|
||||
// Testing
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation "org.junit.vintage:junit-vintage-engine:5.7.1"
|
||||
testImplementation "androidx.test:core-ktx:1.4.0"
|
||||
|
||||
testImplementation "io.mockk:mockk:1.12.1"
|
||||
androidTestImplementation "io.mockk:mockk-android:1.11.0"
|
||||
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.7.1"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.1"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:5.7.1"
|
||||
|
||||
androidTestImplementation "androidx.navigation:navigation-testing:2.3.5"
|
||||
|
||||
testImplementation "io.kotest:kotest-runner-junit5:4.6.2"
|
||||
testImplementation "io.kotest:kotest-assertions-core-jvm:4.6.2"
|
||||
testImplementation "io.kotest:kotest-property-jvm:4.6.2"
|
||||
androidTestImplementation "io.kotest:kotest-assertions-core-jvm:4.6.2"
|
||||
androidTestImplementation "io.kotest:kotest-property-jvm:4.6.2"
|
||||
|
||||
testImplementation 'android.arch.core:core-testing:1.1.1'
|
||||
androidTestImplementation 'android.arch.core:core-testing:1.1.1'
|
||||
debugImplementation 'androidx.test:core-ktx:1.4.0'
|
||||
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.4.0'
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
id("com.google.devtools.ksp")
|
||||
id("kotlin-kapt")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
apply(plugin = "dagger.hilt.android.plugin")
|
||||
apply(plugin = "androidx.navigation.safeargs.kotlin")
|
||||
|
||||
android {
|
||||
compileSdk = ProjectConfig.compileSdk
|
||||
namespace = ProjectConfig.packageName
|
||||
|
||||
defaultConfig {
|
||||
applicationId = ProjectConfig.packageName
|
||||
|
||||
minSdk = ProjectConfig.minSdk
|
||||
targetSdk = ProjectConfig.targetSdk
|
||||
|
||||
versionCode = ProjectConfig.Version.code + 0 // Base app
|
||||
versionName = ProjectConfig.Version.name
|
||||
|
||||
testInstrumentationRunner = "eu.darken.capod.HiltTestRunner"
|
||||
}
|
||||
|
||||
// Enable automatic per-app language preferences generation
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
val basePath = File(System.getProperty("user.home"), ".appconfig/${ProjectConfig.packageName}")
|
||||
create("releaseFoss") {
|
||||
setupCredentials(File(basePath, "signing-foss.properties"))
|
||||
}
|
||||
create("releaseGplay") {
|
||||
setupCredentials(File(basePath, "signing-gplay-upload.properties"))
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions.add("version")
|
||||
productFlavors {
|
||||
create("foss") {
|
||||
dimension = "version"
|
||||
signingConfig = signingConfigs["releaseFoss"]
|
||||
// The info block is encrypted and can only be read by google
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
}
|
||||
create("gplay") {
|
||||
dimension = "version"
|
||||
signingConfig = signingConfigs["releaseGplay"]
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
val customProguardRules = fileTree(File(projectDir, "proguard")) {
|
||||
include("*.pro")
|
||||
}
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
proguardFiles(*customProguardRules.toList().toTypedArray())
|
||||
proguardFiles("proguard-rules-debug.pro")
|
||||
}
|
||||
create("beta") {
|
||||
lint {
|
||||
abortOnError = true
|
||||
fatal.add("StopShip")
|
||||
}
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
proguardFiles(*customProguardRules.toList().toTypedArray())
|
||||
}
|
||||
release {
|
||||
lint {
|
||||
abortOnError = true
|
||||
fatal.add("StopShip")
|
||||
}
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
proguardFiles(*customProguardRules.toList().toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
buildOutputs.all {
|
||||
val variantOutputImpl = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
val variantName: String = variantOutputImpl.name
|
||||
|
||||
if (listOf("release", "beta").any { variantName.lowercase().contains(it) }) {
|
||||
val outputFileName = ProjectConfig.packageName +
|
||||
"-v${defaultConfig.versionName}-${defaultConfig.versionCode}" +
|
||||
"-${variantName.uppercase()}.apk"
|
||||
|
||||
variantOutputImpl.outputFileName = outputFileName
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs = freeCompilerArgs + listOf(
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlin.time.ExperimentalTime",
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-opt-in=kotlin.ExperimentalUnsignedTypes",
|
||||
"-Xannotation-default-target=param-property"
|
||||
)
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
//noinspection WrongGradleMethod
|
||||
tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||
|
||||
addBaseKotlin()
|
||||
|
||||
addDagger()
|
||||
|
||||
addMoshi()
|
||||
|
||||
addOkio()
|
||||
|
||||
addBaseAndroid()
|
||||
addBaseAndroidUi()
|
||||
|
||||
implementation("androidx.core:core-splashscreen:1.0.0-alpha02")
|
||||
|
||||
addNavigation()
|
||||
addBaseWorkManager()
|
||||
|
||||
addTesting()
|
||||
|
||||
"gplayImplementation"("com.android.billingclient:billing:8.0.0")
|
||||
"gplayImplementation"("com.android.billingclient:billing-ktx:8.0.0")
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class UpgradeControlFoss @Inject constructor(
|
||||
upgradedAt = Instant.now(),
|
||||
reason = FossUpgrade.Reason.DONATED
|
||||
)
|
||||
webpageTool.open("https://github.com/d4rken/capod")
|
||||
webpageTool.open("https://github.com/sponsors/d4rken")
|
||||
Toast.makeText(activity, R.string.general_thank_you_label, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
setNegativeButton(R.string.foss_upgrade_alreadydonated_label) { _, _ ->
|
||||
@@ -64,8 +64,11 @@ class UpgradeControlFoss @Inject constructor(
|
||||
override val isPro: Boolean = false,
|
||||
override val upgradedAt: Instant? = null,
|
||||
val upgradeReason: FossUpgrade.Reason? = null,
|
||||
override val error: Throwable? = null,
|
||||
) : UpgradeRepo.Info {
|
||||
override val type: UpgradeRepo.Type = UpgradeRepo.Type.FOSS
|
||||
}
|
||||
|
||||
override fun getSponsorUrl(): String = "https://github.com/sponsors/d4rken"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package eu.darken.capod.debug.autoreport
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
abstract class AutoReportingModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun autoreporting(foss: FossAutoReporting): AutomaticBugReporter
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package eu.darken.capod.debug.autoreport
|
||||
|
||||
import android.app.Application
|
||||
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FossAutoReporting @Inject constructor() : AutomaticBugReporter {
|
||||
override fun setup(application: Application) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
override fun notify(throwable: Throwable) {
|
||||
throw IllegalStateException("Who initliazed this? Without setup no calls to here!")
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Skenk</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ek het reeds geskenk</string>
|
||||
<string name="foss_upgrade_no_money_label">Ek spandeer al my geld op AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">ልገሳ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">አስቀድሜ ልገሳ ፈጽሜአለሁ</string>
|
||||
<string name="foss_upgrade_no_money_label">ገንዘቤን በሙሉ በAirPods ላይ አውጥቻለሁ</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">تبرُّع</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">أنا متبرع بالفعل</string>
|
||||
<string name="foss_upgrade_no_money_label">أُنفق كل أموالي على AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Ахвяраваць</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Я ўжо ахвяраваў</string>
|
||||
<string name="foss_upgrade_no_money_label">Я патраціў усе грошы на AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Дарение</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Вече съм дарил</string>
|
||||
<string name="foss_upgrade_no_money_label">Харча всичките си пари за AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">দান করুন</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">আমি ইতিমধ্যে দান করেছি</string>
|
||||
<string name="foss_upgrade_no_money_label">আমি আমার সব টাকা AirPods এর জন্য খরচ করি</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doner</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Jeg har allerede doneret</string>
|
||||
<string name="foss_upgrade_no_money_label">Jeg bruger alle mine penge på AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Spenden</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ich habe schon gespendet</string>
|
||||
<string name="foss_upgrade_no_money_label">Ich gebe mein ganzes Geld für AirPods aus</string>
|
||||
<string name="foss_upgrade_no_money_label">Hab alles für AirPods ausgegeben</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ya doné</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todo mi dinero en AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Anneta</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Juba annetasin</string>
|
||||
<string name="foss_upgrade_no_money_label">Kulutasin kogu raha AirPodsidele</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Eman dohaintza</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Dagoeneko eman dut dohaintza</string>
|
||||
<string name="foss_upgrade_no_money_label">Nire diru guztia AirPods-etan gastatzen dut</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">کمک مالی</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">کمک مالی کردهام</string>
|
||||
<string name="foss_upgrade_no_money_label">من تمام پولم را خرج ایرپاد میکنم</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Lahjoita</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Olen jo lahjoittanut</string>
|
||||
<string name="foss_upgrade_no_money_label">Käytän kaikki rahani AirPodeihin</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Mag-donate</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Nakapag-donate na ako</string>
|
||||
<string name="foss_upgrade_no_money_label">Ginagastos ko lahat ng pera ko sa AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Xa doei</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todos os meus cartos en AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">दान करना</string>
|
||||
<string name="foss_upgrade_donate_label">दान करें</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">मैंने पहले ही दान कर दिया है</string>
|
||||
<string name="foss_upgrade_no_money_label">मैं अपना सारा पैसा AirPods पर खर्च करता हूं</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donirajte</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Već sam donirao</string>
|
||||
<string name="foss_upgrade_no_money_label">Sav svoj novac trošim na AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Adomány</string>
|
||||
<string name="foss_upgrade_donate_label">Adományoz</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Már adományoztam</string>
|
||||
<string name="foss_upgrade_no_money_label">Minden pénzemet AirPodokra költöm</string>
|
||||
<string name="foss_upgrade_no_money_label">Minden pénzemet az AirPods-ra költöttem</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Նվիրաբերել</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ես արդեն նվիրաբերել եմ</string>
|
||||
<string name="foss_upgrade_no_money_label">Ես իմ ամբողջ գումարը ծախսում եմ AirPods-ի վրա</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Gefa</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ég hef þegar gefið</string>
|
||||
<string name="foss_upgrade_no_money_label">Ég eyði öllum peningunum mínum í AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">寄付</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">すでに寄付しました</string>
|
||||
<string name="foss_upgrade_no_money_label">AirPodsに全財産をつぎ込んでいます</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">შემოწირულობა</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">მე უკვე შემოწირული მაქვს</string>
|
||||
<string name="foss_upgrade_no_money_label">მთელ ჩემს ფულს AirPods-ებზე ვხარჯავ</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">បរិច្ចាគ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ខ្ញុំបានបរិច្ចាគរួចហើយ</string>
|
||||
<string name="foss_upgrade_no_money_label">ខ្ញុំចំណាយលុយទាំងអស់របស់ខ្ញុំលើ AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Beşdar bike</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Min berê bexş kir</string>
|
||||
<string name="foss_upgrade_no_money_label">Ez hemû pereyê xwe li AirPods xerc dikim</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">ದಾನ ಮಾಡಿ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ನಾನು ಈಗಾಗಲೇ ದಾನ ಮಾಡಿದ್ದೇನೆ</string>
|
||||
<string name="foss_upgrade_no_money_label">ನಾನು ನನ್ನ ಎಲ್ಲಾ ಹಣವನ್ನು AirPods ಗಾಗಿ ಖರ್ಚು ಮಾಡುತ್ತೇನೆ</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Демөөрчүлүк кылуу</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Мен буга чейин демөөрчүлүк кылгам</string>
|
||||
<string name="foss_upgrade_no_money_label">Мен бүт акчамды AirPods үчүн короттум</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">ບໍລິຈາກ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ຂ້ອຍໄດ້ບໍລິຈາກແລ້ວ</string>
|
||||
<string name="foss_upgrade_no_money_label">ຂ້ອຍໃຊ້ເງິນທັງໝົດຂອງຂ້ອຍໃສ່ AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Aukoti</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Aš jau paaukojau</string>
|
||||
<string name="foss_upgrade_no_money_label">Visus savo pinigus išleidžiu „AirPods“ ausinėms</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Ziedot</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Es jau ziedoju</string>
|
||||
<string name="foss_upgrade_no_money_label">Es iztērēju visu savu naudu AirPods austiņām</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Донирај</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Веќе донирав</string>
|
||||
<string name="foss_upgrade_no_money_label">Ги трошам сите пари на AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">സംഭാവന ചെയ്യുക</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ഞാൻ ഇതിനകം സംഭാവന ചെയ്തു</string>
|
||||
<string name="foss_upgrade_no_money_label">ഞാൻ എൻ്റെ എല്ലാ പണവും AirPods-നായി ചെലവഴിക്കുന്നു</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Хандивлах</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Би аль хэдийн хандивласан</string>
|
||||
<string name="foss_upgrade_no_money_label">Би бүх мөнгөө AirPods-д зарцуулдаг</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">दान करा</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">मी आधीच दान केले आहे</string>
|
||||
<string name="foss_upgrade_no_money_label">मी माझे सर्व पैसे AirPods वर खर्च करतो</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Derma</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Saya sudah menderma</string>
|
||||
<string name="foss_upgrade_donate_label">Sumbang</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Saya telah menyumbang</string>
|
||||
<string name="foss_upgrade_no_money_label">Saya belanjakan semua wang saya untuk AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">လှူဒါန်းပါ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ကျွန်တော် လှူပြီးပါပြီ</string>
|
||||
<string name="foss_upgrade_no_money_label">ကျွန်တော် ပိုက်ဆံအားလုံးကို AirPods မှာ သုံးတယ်</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">दान गर्नुहोस्</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">मैले पहिले नै दान गरिसकेको छु</string>
|
||||
<string name="foss_upgrade_no_money_label">म आफ्नो सबै पैसा AirPods मा खर्च गर्छु</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Dotacja</string>
|
||||
<string name="foss_upgrade_donate_label">Wsparcie</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Już się zrzuciłem</string>
|
||||
<string name="foss_upgrade_no_money_label">Wydałem wszystkie pieniądze na AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Já doei</string>
|
||||
<string name="foss_upgrade_no_money_label">Gastei todo o meu dinheiro em AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donează</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Am donat deja</string>
|
||||
<string name="foss_upgrade_no_money_label">Mi-am cheltuit toți banii pe AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Dona</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Apo giai donadu</string>
|
||||
<string name="foss_upgrade_no_money_label">Apo spèndidu totu is soddos meos pro is AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">පරිත්යාග</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">මම දැනටමත් පරිත්යාග කර ඇත</string>
|
||||
<string name="foss_upgrade_no_money_label">මම මගේ මුදල් සියල්ල AirPods සඳහා වියදම් කරමි</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doniraj</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Sem že doniral</string>
|
||||
<string name="foss_upgrade_no_money_label">Ves svoj denar porabim za AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Dhuro</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Unë tashmë kam dhuruar</string>
|
||||
<string name="foss_upgrade_no_money_label">Unë i shpenzoj të gjitha paratë e mia për AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Донације</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Већ сам донирао/ла</string>
|
||||
<string name="foss_upgrade_no_money_label">Сав новац трошим на АирПодс</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donera</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Jag har redan donerat</string>
|
||||
<string name="foss_upgrade_no_money_label">Jag spenderar alla mina pengar på AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Changia</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Nimeshachangia tayari</string>
|
||||
<string name="foss_upgrade_no_money_label">Natumia pesa zangu zote kwenye AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">நன்கொடை</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">நான் ஏற்கனவே நன்கொடை அளித்துவிட்டேன்</string>
|
||||
<string name="foss_upgrade_no_money_label">எனது பணத்தை எல்லாம் ஏர்பாட்களில் செலவிடுகிறேன்</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">విరాళం ఇవ్వండి</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">నేను ఇప్పటికే విరాళం ఇచ్చాను</string>
|
||||
<string name="foss_upgrade_no_money_label">నా డబ్బు అంతా ఎయిర్పాడ్స్కే ఖర్చు చేస్తాను</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">บริจาค</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ฉันบริจาคแล้ว</string>
|
||||
<string name="foss_upgrade_no_money_label">ฉันใช้เงินทั้งหมดไปกับ AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Bağış</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ben zaten bağışladım</string>
|
||||
<string name="foss_upgrade_no_money_label">Tüm paramı AirPod\'lara harcıyorum</string>
|
||||
<string name="foss_upgrade_donate_label">Bağış yap</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Zaten bağış yaptım</string>
|
||||
<string name="foss_upgrade_no_money_label">Tüm paramı AirPods\'a harcıyorum</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">عطیہ کریں۔</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">میں نے پہلے ہی عطیہ کر دیا ہے۔</string>
|
||||
<string name="foss_upgrade_no_money_label">میں اپنی تمام رقم ایئر پوڈز پر خرچ کرتا ہوں۔</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Xayriya qilish</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Men allaqachon xayriya qildim</string>
|
||||
<string name="foss_upgrade_no_money_label">Men butun pulimni AirPods uchun sarflayman</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">資助</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">我已經資助了</string>
|
||||
<string name="foss_upgrade_no_money_label">我已經為 AirPods 傾家蕩產了</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Nikela</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Senginikele kakade</string>
|
||||
<string name="foss_upgrade_no_money_label">Ngisebenzisa yonke imali yami kuma-AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package eu.darken.capod.common.upgrade.core
|
||||
|
||||
import com.android.billingclient.api.Purchase
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.upgrade.core.data.PurchasedSku
|
||||
import eu.darken.capod.common.upgrade.core.data.Sku
|
||||
|
||||
|
||||
fun Purchase.toPurchasedSku(): Collection<PurchasedSku> = skus.map {
|
||||
PurchasedSku(Sku(it), this)
|
||||
}
|
||||
|
||||
private val TAG: String = logTag("Upgrade", "Gplay", "Billing", "Extensions")
|
||||
@@ -17,14 +17,16 @@ import eu.darken.capod.common.upgrade.core.data.BillingData
|
||||
import eu.darken.capod.common.upgrade.core.data.BillingDataRepo
|
||||
import eu.darken.capod.common.upgrade.core.data.PurchasedSku
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.retryWhen
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.pow
|
||||
|
||||
@Singleton
|
||||
class UpgradeRepoGplay @Inject constructor(
|
||||
@@ -49,25 +51,28 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
lastProStateAt = now
|
||||
Info(billingData = data)
|
||||
}
|
||||
(now - lastProStateAt) < 6 * 60 * 1000L -> { // 6 hours
|
||||
|
||||
(now - lastProStateAt) < 6 * 60 * 60 * 1000L -> { // 6 hours
|
||||
log(TAG, VERBOSE) { "We are not pro, but were recently, did GPlay try annoy us again?" }
|
||||
Info(gracePeriod = true, billingData = null)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Info(billingData = data)
|
||||
}
|
||||
}
|
||||
}
|
||||
.catch {
|
||||
// Ignore Google Play errors if the last pro state was recent
|
||||
.retryWhen { error, attempt ->
|
||||
val now = System.currentTimeMillis()
|
||||
log(TAG) { "now=$now, lastProStateAt=$lastProStateAt, error=$it" }
|
||||
if ((now - lastProStateAt) < 6 * 60 * 60 * 1000L) { // 6 hours
|
||||
log(TAG) { "now=$now, lastProStateAt=$lastProStateAt, attempt=$attempt, error=$error" }
|
||||
if ((now - lastProStateAt) < 24 * 60 * 60 * 1000L) { // 24 hours
|
||||
log(TAG, VERBOSE) { "We are not pro, but were recently, and just and an error, what is GPlay doing???" }
|
||||
emit(Info(gracePeriod = true, billingData = null))
|
||||
} else {
|
||||
throw it
|
||||
emit(Info(billingData = null, error = if (attempt == 0L) error else null))
|
||||
}
|
||||
delay(30_000L * 2.0.pow(attempt.toDouble()).toLong())
|
||||
true
|
||||
}
|
||||
.replayingShare(scope)
|
||||
|
||||
@@ -117,13 +122,14 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
data class Info(
|
||||
private val gracePeriod: Boolean = false,
|
||||
private val billingData: BillingData?,
|
||||
override val error: Throwable? = null,
|
||||
) : UpgradeRepo.Info {
|
||||
|
||||
override val type: UpgradeRepo.Type
|
||||
get() = UpgradeRepo.Type.GPLAY
|
||||
|
||||
override val isPro: Boolean
|
||||
get() = billingData?.getProSku() != null
|
||||
get() = billingData?.getProSku() != null || gracePeriod
|
||||
|
||||
override val upgradedAt: Instant?
|
||||
get() = billingData
|
||||
|
||||
+49
-29
@@ -1,7 +1,15 @@
|
||||
package eu.darken.capod.common.upgrade.core.client
|
||||
|
||||
import android.app.Activity
|
||||
import com.android.billingclient.api.*
|
||||
import com.android.billingclient.api.AcknowledgePurchaseParams
|
||||
import com.android.billingclient.api.BillingClient
|
||||
import com.android.billingclient.api.BillingFlowParams
|
||||
import com.android.billingclient.api.BillingResult
|
||||
import com.android.billingclient.api.ProductDetails
|
||||
import com.android.billingclient.api.Purchase
|
||||
import com.android.billingclient.api.QueryProductDetailsParams
|
||||
import com.android.billingclient.api.QueryPurchasesParams
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
@@ -19,17 +27,25 @@ data class BillingClientConnection(
|
||||
) {
|
||||
private val purchasesLocal = MutableStateFlow<Collection<Purchase>>(emptySet())
|
||||
val purchases: Flow<Collection<Purchase>> = combine(purchasesGlobal, purchasesLocal) { global, local ->
|
||||
val combined = mutableMapOf<String, Purchase>()
|
||||
global.plus(local).toSet().sortedByDescending { it.purchaseTime }.forEach { purchase ->
|
||||
combined[purchase.orderId] = purchase
|
||||
}
|
||||
combined.values
|
||||
val combined = mutableSetOf<Purchase>()
|
||||
|
||||
combined.addAll(local)
|
||||
|
||||
global
|
||||
.let { purchases -> purchases.filter { it.purchaseState == Purchase.PurchaseState.PURCHASED } }
|
||||
.let { combined.addAll(it) }
|
||||
|
||||
combined.sortedByDescending { it.purchaseTime }
|
||||
}
|
||||
.setupCommonEventHandlers(TAG) { "purchases" }
|
||||
|
||||
suspend fun queryPurchases(): Collection<Purchase> {
|
||||
val params = QueryPurchasesParams.newBuilder()
|
||||
.setProductType(BillingClient.ProductType.INAPP)
|
||||
.build()
|
||||
|
||||
val (result: BillingResult, purchases) = suspendCoroutine<Pair<BillingResult, Collection<Purchase>?>> { continuation ->
|
||||
client.queryPurchasesAsync(BillingClient.SkuType.INAPP) { result, purchases ->
|
||||
client.queryPurchasesAsync(params) { result, purchases ->
|
||||
continuation.resume(result to purchases)
|
||||
}
|
||||
}
|
||||
@@ -38,7 +54,7 @@ data class BillingClientConnection(
|
||||
|
||||
if (!result.isSuccess) {
|
||||
log(TAG, WARN) { "queryPurchases() failed" }
|
||||
throw BillingClientException(result)
|
||||
throw BillingResultException(result)
|
||||
} else {
|
||||
requireNotNull(purchases)
|
||||
}
|
||||
@@ -47,34 +63,32 @@ data class BillingClientConnection(
|
||||
return purchases
|
||||
}
|
||||
|
||||
suspend fun acknowledgePurchase(purchase: Purchase): BillingResult {
|
||||
suspend fun acknowledgePurchase(purchase: Purchase) {
|
||||
val ack = AcknowledgePurchaseParams.newBuilder().apply {
|
||||
setPurchaseToken(purchase.purchaseToken)
|
||||
}.build()
|
||||
|
||||
val ackResult = suspendCoroutine<BillingResult> { continuation ->
|
||||
val result = suspendCoroutine<BillingResult> { continuation ->
|
||||
client.acknowledgePurchase(ack) { continuation.resume(it) }
|
||||
}
|
||||
log(TAG) {
|
||||
"acknowledgePurchase(purchase=$purchase): code=${ackResult.responseCode}, message=${ackResult.debugMessage})"
|
||||
}
|
||||
|
||||
if (!ackResult.isSuccess) {
|
||||
throw BillingClientException(ackResult)
|
||||
}
|
||||
log(TAG, INFO) { "acknowledgePurchase($purchase): code=${result.responseCode} (${result.debugMessage})" }
|
||||
|
||||
return ackResult
|
||||
if (!result.isSuccess) throw BillingResultException(result)
|
||||
}
|
||||
|
||||
suspend fun querySku(sku: Sku): Sku.Details {
|
||||
val skuParams = SkuDetailsParams.newBuilder().apply {
|
||||
setType(BillingClient.SkuType.INAPP)
|
||||
setSkusList(listOf(sku.id))
|
||||
val productDetails = QueryProductDetailsParams.Product.newBuilder().apply {
|
||||
setProductType(BillingClient.ProductType.INAPP)
|
||||
setProductId(sku.id)
|
||||
}.build()
|
||||
|
||||
val (result, details) = suspendCoroutine<Pair<BillingResult, Collection<SkuDetails>?>> { continuation ->
|
||||
client.querySkuDetailsAsync(skuParams) { skuResult, skuDetails ->
|
||||
continuation.resume(skuResult to skuDetails)
|
||||
val params = QueryProductDetailsParams.newBuilder().setProductList(listOf(productDetails)).build()
|
||||
|
||||
val (result, details) = suspendCoroutine<Pair<BillingResult, List<ProductDetails>>> { continuation ->
|
||||
client.queryProductDetailsAsync(params) { billingResult, queryResult ->
|
||||
val productDetailsList = queryResult.productDetailsList ?: emptyList()
|
||||
continuation.resume(billingResult to productDetailsList)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,9 +96,9 @@ data class BillingClientConnection(
|
||||
"querySku(sku=$sku): code=${result.responseCode}, debug=${result.debugMessage}), skuDetails=$details"
|
||||
}
|
||||
|
||||
if (!result.isSuccess) throw BillingClientException(result)
|
||||
if (!result.isSuccess) throw BillingResultException(result)
|
||||
|
||||
if (details.isNullOrEmpty()) throw IllegalStateException("Unknown SKU, no details available.")
|
||||
if (details.isEmpty()) throw IllegalStateException("Unknown SKU, no details available.")
|
||||
|
||||
return Sku.Details(sku, details)
|
||||
}
|
||||
@@ -97,10 +111,16 @@ data class BillingClientConnection(
|
||||
|
||||
suspend fun launchBillingFlow(activity: Activity, skuDetails: Sku.Details): BillingResult {
|
||||
log(TAG) { "launchBillingFlow(activity=$activity, skuDetails=$skuDetails)" }
|
||||
return client.launchBillingFlow(
|
||||
activity,
|
||||
BillingFlowParams.newBuilder().setSkuDetails(skuDetails.details.single()).build()
|
||||
)
|
||||
|
||||
val productParams = BillingFlowParams.ProductDetailsParams.newBuilder().apply {
|
||||
setProductDetails(skuDetails.details.first())
|
||||
}.build()
|
||||
|
||||
val billingFlowParams = BillingFlowParams.newBuilder().apply {
|
||||
setProductDetailsParamsList(listOf(productParams))
|
||||
}.build()
|
||||
|
||||
return client.launchBillingFlow(activity, billingFlowParams)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+56
-41
@@ -1,9 +1,11 @@
|
||||
package eu.darken.capod.common.upgrade.core.client
|
||||
|
||||
import android.content.Context
|
||||
import com.android.billingclient.api.BillingClient.*
|
||||
import com.android.billingclient.api.BillingClient.BillingResponseCode
|
||||
import com.android.billingclient.api.BillingClient.newBuilder
|
||||
import com.android.billingclient.api.BillingClientStateListener
|
||||
import com.android.billingclient.api.BillingResult
|
||||
import com.android.billingclient.api.PendingPurchasesParams
|
||||
import com.android.billingclient.api.Purchase
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||
@@ -13,12 +15,15 @@ import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.retryWhen
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@Singleton
|
||||
class BillingClientConnectionProvider @Inject constructor(
|
||||
@@ -29,7 +34,11 @@ class BillingClientConnectionProvider @Inject constructor(
|
||||
val purchasePublisher = MutableStateFlow<Collection<Purchase>>(emptySet())
|
||||
|
||||
val client = newBuilder(context).apply {
|
||||
enablePendingPurchases()
|
||||
enablePendingPurchases(
|
||||
PendingPurchasesParams.newBuilder()
|
||||
.enableOneTimeProducts()
|
||||
.build()
|
||||
)
|
||||
setListener { result, purchases ->
|
||||
if (result.isSuccess) {
|
||||
log(TAG) {
|
||||
@@ -44,36 +53,40 @@ class BillingClientConnectionProvider @Inject constructor(
|
||||
}
|
||||
}.build()
|
||||
|
||||
val connectionResult = suspendCoroutine<BillingResult> { continuation ->
|
||||
log(TAG, VERBOSE) { "startConnection(...)" }
|
||||
client.startConnection(object : BillingClientStateListener {
|
||||
override fun onBillingSetupFinished(result: BillingResult) {
|
||||
log(TAG, VERBOSE) {
|
||||
"onBillingSetupFinished(code=${result.responseCode}, message=${result.debugMessage})"
|
||||
|
||||
log(TAG, VERBOSE) { "startConnection(...)" }
|
||||
client.startConnection(object : BillingClientStateListener {
|
||||
override fun onBillingSetupFinished(result: BillingResult) {
|
||||
log(TAG, VERBOSE) {
|
||||
"onBillingSetupFinished(code=${result.responseCode}, message=${result.debugMessage})"
|
||||
}
|
||||
|
||||
when (result.responseCode) {
|
||||
BillingResponseCode.OK -> {
|
||||
val connection = BillingClientConnection(client, purchasePublisher)
|
||||
|
||||
trySendBlocking(connection)
|
||||
|
||||
launch {
|
||||
try {
|
||||
purchasePublisher.value = connection.queryPurchases()
|
||||
log(TAG) { "Initial IAP query successful." }
|
||||
} catch (e: Exception) {
|
||||
log(TAG, ERROR) { "Initial IAP query failed:\n${e.asLog()}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
close(BillingResultException(result))
|
||||
}
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBillingServiceDisconnected() {
|
||||
log(TAG, VERBOSE) { "onBillingServiceDisconnected() " }
|
||||
close(CancellationException("Billing service disconnected"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val billingClientConnection = when (connectionResult.responseCode) {
|
||||
BillingResponseCode.OK -> BillingClientConnection(client, purchasePublisher)
|
||||
else -> throw BillingClientException(connectionResult)
|
||||
}
|
||||
|
||||
try {
|
||||
purchasePublisher.value = billingClientConnection.queryPurchases()
|
||||
log(TAG) { "Initial IAP query successful." }
|
||||
} catch (e: Exception) {
|
||||
log(TAG, ERROR) { "Initial IAP query failed:\n${e.asLog()}" }
|
||||
}
|
||||
|
||||
send(billingClientConnection)
|
||||
override fun onBillingServiceDisconnected() {
|
||||
log(TAG, VERBOSE) { "onBillingServiceDisconnected() " }
|
||||
close(BillingException("Billing service disconnected"))
|
||||
}
|
||||
})
|
||||
|
||||
log(TAG) { "Awaiting close." }
|
||||
awaitClose {
|
||||
@@ -85,26 +98,28 @@ class BillingClientConnectionProvider @Inject constructor(
|
||||
val connection: Flow<BillingClientConnection> = connectionProvider
|
||||
.setupCommonEventHandlers(TAG) { "connection" }
|
||||
.retryWhen { cause, attempt ->
|
||||
log(TAG) { "Billing client connection error: ${cause.asLog()}" }
|
||||
|
||||
if (cause is CancellationException) {
|
||||
log(TAG) { "BillingClient connection cancelled." }
|
||||
return@retryWhen false
|
||||
}
|
||||
if (attempt > 5) {
|
||||
log(TAG, WARN) { "Reached attempt limit: $attempt due to $cause" }
|
||||
|
||||
if (cause !is BillingException) {
|
||||
log(TAG, WARN) { "Unknown exception type: $cause" }
|
||||
return@retryWhen false
|
||||
}
|
||||
if (cause !is BillingClientException) {
|
||||
log(TAG, WARN) { "Unknown BillingClient exception type: $cause" }
|
||||
return@retryWhen false
|
||||
} else {
|
||||
log(TAG) { "BillingClient exception: $cause; ${cause.result}" }
|
||||
}
|
||||
|
||||
if (cause.result.responseCode == BillingResponseCode.BILLING_UNAVAILABLE) {
|
||||
if (cause is BillingResultException && cause.result.isGplayUnavailablePermanent) {
|
||||
log(TAG) { "Got BILLING_UNAVAILABLE while trying to connect client." }
|
||||
return@retryWhen false
|
||||
}
|
||||
|
||||
if (attempt > 5) {
|
||||
log(TAG, WARN) { "Reached attempt limit: $attempt due to $cause" }
|
||||
return@retryWhen false
|
||||
}
|
||||
|
||||
log(TAG) { "Will retry BillingClient connection... *sigh*" }
|
||||
delay(3000 * attempt)
|
||||
true
|
||||
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
package eu.darken.capod.common.upgrade.core.client
|
||||
|
||||
import com.android.billingclient.api.BillingResult
|
||||
|
||||
class BillingClientException(val result: BillingResult) : Exception() {
|
||||
override val message: String?
|
||||
get() = result.debugMessage
|
||||
|
||||
override fun toString(): String =
|
||||
"BillingClientException(code=${result.responseCode}, message=${result.debugMessage})"
|
||||
}
|
||||
+11
-1
@@ -4,4 +4,14 @@ import com.android.billingclient.api.BillingClient
|
||||
import com.android.billingclient.api.BillingResult
|
||||
|
||||
internal val BillingResult.isSuccess: Boolean
|
||||
get() = responseCode == BillingClient.BillingResponseCode.OK
|
||||
get() = responseCode == BillingClient.BillingResponseCode.OK
|
||||
|
||||
internal val BillingResult.isGplayUnavailableTemporary: Boolean
|
||||
get() = setOf(
|
||||
BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
|
||||
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
|
||||
BillingClient.BillingResponseCode.SERVICE_TIMEOUT
|
||||
).contains(responseCode)
|
||||
|
||||
internal val BillingResult.isGplayUnavailablePermanent: Boolean
|
||||
get() = responseCode == BillingClient.BillingResponseCode.BILLING_UNAVAILABLE
|
||||
@@ -0,0 +1,15 @@
|
||||
package eu.darken.capod.common.upgrade.core.client
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.error.HasLocalizedError
|
||||
import eu.darken.capod.common.error.LocalizedError
|
||||
|
||||
open class BillingException(override val message: String) : Exception(), HasLocalizedError {
|
||||
|
||||
override fun getLocalizedError(context: Context): LocalizedError = LocalizedError(
|
||||
throwable = this,
|
||||
label = context.getString(R.string.upgrades_gplay_billing_error_label),
|
||||
description = context.getString(R.string.upgrades_gplay_billing_error_description, message)
|
||||
)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package eu.darken.capod.common.upgrade.core.client
|
||||
|
||||
import android.content.Context
|
||||
import com.android.billingclient.api.BillingResult
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.error.LocalizedError
|
||||
|
||||
class BillingResultException(val result: BillingResult) : BillingException(result.debugMessage) {
|
||||
|
||||
override fun toString(): String =
|
||||
"BillingResultException(code=${result.responseCode}, message=${result.debugMessage})"
|
||||
|
||||
override fun getLocalizedError(context: Context): LocalizedError = LocalizedError(
|
||||
throwable = this,
|
||||
label = context.getString(R.string.upgrades_gplay_billing_result_error_label),
|
||||
description = context.getString(R.string.upgrades_gplay_billing_result_error_description, result)
|
||||
)
|
||||
}
|
||||
+5
-9
@@ -7,13 +7,9 @@ import eu.darken.capod.common.error.LocalizedError
|
||||
|
||||
class GplayServiceUnavailableException(cause: Throwable) : Exception("Google Play services are unavailable.", cause),
|
||||
HasLocalizedError {
|
||||
override fun getLocalizedError(context: Context): LocalizedError {
|
||||
return LocalizedError(
|
||||
throwable = this,
|
||||
label = "Google Play Services Unavailable",
|
||||
description = context.getString(R.string.upgrades_gplay_unavailable_error)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override fun getLocalizedError(context: Context): LocalizedError = LocalizedError(
|
||||
throwable = this,
|
||||
label = "Google Play Services Unavailable",
|
||||
description = context.getString(R.string.upgrades_gplay_unavailable_error)
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package eu.darken.capod.common.upgrade.core.data
|
||||
|
||||
import com.android.billingclient.api.Purchase
|
||||
import eu.darken.capod.common.upgrade.core.toPurchasedSku
|
||||
|
||||
data class BillingData(
|
||||
val purchases: Collection<Purchase>
|
||||
) {
|
||||
val purchasedSkus: Collection<PurchasedSku>
|
||||
get() = purchases.map { it.toPurchasedSku() }.flatten()
|
||||
|
||||
private fun Purchase.toPurchasedSku(): Collection<PurchasedSku> = skus.map {
|
||||
PurchasedSku(Sku(it), this)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package eu.darken.capod.common.upgrade.core.data
|
||||
|
||||
import android.app.Activity
|
||||
import com.android.billingclient.api.*
|
||||
import com.android.billingclient.api.BillingClient.*
|
||||
import eu.darken.capod.common.coroutine.AppScope
|
||||
import eu.darken.capod.common.debug.Bugs
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||
@@ -11,9 +9,7 @@ import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.flow.replayingShare
|
||||
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
||||
import eu.darken.capod.common.upgrade.core.client.BillingClientConnectionProvider
|
||||
import eu.darken.capod.common.upgrade.core.client.BillingClientException
|
||||
import eu.darken.capod.common.upgrade.core.client.GplayServiceUnavailableException
|
||||
import eu.darken.capod.common.upgrade.core.client.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -54,37 +50,34 @@ class BillingDataRepo @Inject constructor(
|
||||
}
|
||||
.forEach {
|
||||
log(TAG, INFO) { "Acknowledging purchase: $it" }
|
||||
|
||||
try {
|
||||
client.acknowledgePurchase(it)
|
||||
} catch (e: Exception) {
|
||||
log(TAG, ERROR) { "Failed to ancknowledge purchase: $it\n${e.asLog()}" }
|
||||
}
|
||||
client.acknowledgePurchase(it)
|
||||
}
|
||||
}
|
||||
.setupCommonEventHandlers(TAG) { "connection-acks" }
|
||||
.retryWhen { cause, attempt ->
|
||||
log(TAG, ERROR) { "Failed to acknowledge purchase: ${cause.asLog()}" }
|
||||
|
||||
if (cause is CancellationException) {
|
||||
log(TAG) { "Ack was cancelled (appScope?) cancelled." }
|
||||
return@retryWhen false
|
||||
}
|
||||
|
||||
if (attempt > 5) {
|
||||
log(TAG, WARN) { "Reached attempt limit: $attempt due to $cause" }
|
||||
return@retryWhen false
|
||||
}
|
||||
if (cause !is BillingClientException) {
|
||||
log(TAG, WARN) { "Unknown BillingClient exception type: $cause" }
|
||||
|
||||
if (cause !is BillingException) {
|
||||
log(TAG, WARN) { "Unknown exception type: $cause" }
|
||||
return@retryWhen false
|
||||
} else {
|
||||
log(TAG) { "BillingClient exception: $cause; ${cause.result}" }
|
||||
}
|
||||
|
||||
if (cause.result.responseCode == BillingResponseCode.BILLING_UNAVAILABLE) {
|
||||
if (cause is BillingResultException && cause.result.isGplayUnavailablePermanent) {
|
||||
log(TAG) { "Got BILLING_UNAVAILABLE while trying to ACK purchase." }
|
||||
return@retryWhen false
|
||||
}
|
||||
|
||||
log(TAG) { "Will retry ACK" }
|
||||
log(TAG) { "Will retry ACK (attempt=$attempt)" }
|
||||
delay(3000 * attempt)
|
||||
true
|
||||
}
|
||||
@@ -109,7 +102,7 @@ class BillingDataRepo @Inject constructor(
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "Failed to start IAP flow:\n${e.asLog()}" }
|
||||
val ignoredCodes = listOf(3, 6)
|
||||
if (e !is BillingClientException || !e.result.responseCode.let { ignoredCodes.contains(it) }) {
|
||||
if (e !is BillingResultException || !e.result.responseCode.let { ignoredCodes.contains(it) }) {
|
||||
Bugs.report(TAG, "IAP flow failed for $sku", e)
|
||||
}
|
||||
|
||||
@@ -120,16 +113,14 @@ class BillingDataRepo @Inject constructor(
|
||||
companion object {
|
||||
val TAG: String = logTag("Upgrade", "Gplay", "Billing", "DataRepo")
|
||||
|
||||
internal fun Throwable.tryMapUserFriendly(): Throwable {
|
||||
if (this !is BillingClientException) return this
|
||||
|
||||
return when (result.responseCode) {
|
||||
BillingResponseCode.BILLING_UNAVAILABLE,
|
||||
BillingResponseCode.SERVICE_UNAVAILABLE,
|
||||
BillingResponseCode.SERVICE_DISCONNECTED,
|
||||
BillingResponseCode.SERVICE_TIMEOUT -> GplayServiceUnavailableException(this)
|
||||
else -> this
|
||||
internal fun Throwable.tryMapUserFriendly(): Throwable = when {
|
||||
this is BillingResultException && this.result.isGplayUnavailableTemporary -> {
|
||||
GplayServiceUnavailableException(this)
|
||||
}
|
||||
this is BillingResultException && this.result.isGplayUnavailablePermanent -> {
|
||||
GplayServiceUnavailableException(this)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.darken.capod.common.upgrade.core.data
|
||||
|
||||
import com.android.billingclient.api.Purchase
|
||||
import eu.darken.capod.common.upgrade.core.data.Sku
|
||||
|
||||
data class PurchasedSku(val sku: Sku, val purchase: Purchase) {
|
||||
override fun toString(): String = "IAP(sku=$sku, purchase=${purchase.skus})"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package eu.darken.capod.common.upgrade.core.data
|
||||
|
||||
import com.android.billingclient.api.SkuDetails
|
||||
import com.android.billingclient.api.ProductDetails
|
||||
|
||||
data class Sku(
|
||||
val id: String
|
||||
) {
|
||||
data class Details(
|
||||
val sku: Sku,
|
||||
val details: Collection<SkuDetails>,
|
||||
val details: Collection<ProductDetails>,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package eu.darken.capod.debug.autoreport
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
abstract class AutoReportingModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun autoreporting(foss: GplayAutoReporting): AutomaticBugReporter
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package eu.darken.capod.debug.autoreport
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.InstallId
|
||||
import eu.darken.capod.common.debug.Bugs
|
||||
import eu.darken.capod.common.debug.DebugSettings
|
||||
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class GplayAutoReporting @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val debugSettings: DebugSettings,
|
||||
private val installId: InstallId,
|
||||
) : AutomaticBugReporter {
|
||||
|
||||
override fun setup(application: Application) {
|
||||
val isEnabled = debugSettings.isAutoReportingEnabled.value
|
||||
log(TAG) { "setup(): isEnabled=$isEnabled" }
|
||||
|
||||
if (!isEnabled) return
|
||||
|
||||
// Currently no 3rd party bug tracking
|
||||
|
||||
Bugs.reporter = this
|
||||
}
|
||||
|
||||
override fun notify(throwable: Throwable) {
|
||||
log(TAG, WARN) { "notify($throwable)" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Debug", "AutoReport")
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user