Compare commits
458 Commits
ghcr/serve
...
optimize-w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29c67bd5dd | ||
|
|
da15842597 | ||
|
|
eb959046cd | ||
|
|
1178ae26d0 | ||
|
|
4a2f5b4676 | ||
|
|
9fe15d7ff0 | ||
|
|
28a2afe275 | ||
|
|
c072097c11 | ||
|
|
0fece5666b | ||
|
|
26e564aec2 | ||
|
|
dd420a80a4 | ||
|
|
3dc0620e18 | ||
|
|
173d075f8b | ||
|
|
48283282e5 | ||
|
|
fa555c448f | ||
|
|
6f6770d677 | ||
|
|
0b894e9724 | ||
|
|
0670550cb1 | ||
|
|
45783cf527 | ||
|
|
1615779eb8 | ||
|
|
02e4c9d8fd | ||
|
|
6eab6457ee | ||
|
|
25490a7238 | ||
|
|
f519ff8a51 | ||
|
|
afebe1ade1 | ||
|
|
3862644dd5 | ||
|
|
274a7d207d | ||
|
|
add2f0c8de | ||
|
|
8e807616e0 | ||
|
|
70f4325c71 | ||
|
|
38ea2248b8 | ||
|
|
9600b26359 | ||
|
|
5b3e996aaa | ||
|
|
4d4cce091f | ||
|
|
aaca140d1b | ||
|
|
596ffcd4c4 | ||
|
|
41ef85a294 | ||
|
|
f722d82835 | ||
|
|
cbb3096534 | ||
|
|
f635e1e856 | ||
|
|
c6734a5cb7 | ||
|
|
e26b4796d3 | ||
|
|
99c0194c0f | ||
|
|
e824c02d7f | ||
|
|
a11f66b51d | ||
|
|
f202fef266 | ||
|
|
ff8cfd3e87 | ||
|
|
431ab7fcc7 | ||
|
|
2ac1d58dac | ||
|
|
5533e6a71d | ||
|
|
a8ae0727a8 | ||
|
|
6955788724 | ||
|
|
f6dd35f5e7 | ||
|
|
9148916d88 | ||
|
|
68545f8947 | ||
|
|
76c5c12c53 | ||
|
|
8d749a2dc8 | ||
|
|
56af818482 | ||
|
|
fc1096c985 | ||
|
|
88260a05e3 | ||
|
|
4b5f91a428 | ||
|
|
29b12fc6b5 | ||
|
|
c3eec71d60 | ||
|
|
2fccdee0d6 | ||
|
|
8f053e7a7b | ||
|
|
de7291f5d4 | ||
|
|
e09c952198 | ||
|
|
28a842b006 | ||
|
|
19eb342f59 | ||
|
|
2b82c79be9 | ||
|
|
86a9dee49d | ||
|
|
33b1a7e4f8 | ||
|
|
de783e91dc | ||
|
|
554c8f4b7a | ||
|
|
f438142646 | ||
|
|
aa6c010562 | ||
|
|
2830c89bde | ||
|
|
4de530b882 | ||
|
|
62d7c87dc7 | ||
|
|
3a1b6dbf15 | ||
|
|
58182cc8ab | ||
|
|
5e0bba390b | ||
|
|
df6a3b94db | ||
|
|
72b7e12768 | ||
|
|
3a7d82a799 | ||
|
|
5f1cfb9ba5 | ||
|
|
298e3695c7 | ||
|
|
621713d0b4 | ||
|
|
34813d2fae | ||
|
|
bb177bc3f6 | ||
|
|
d8e4418d78 | ||
|
|
9771a5bc5d | ||
|
|
65f7e3f6c6 | ||
|
|
49b9d83f05 | ||
|
|
273d7bd00a | ||
|
|
4e8991dc10 | ||
|
|
aa4207f878 | ||
|
|
3176ba8a93 | ||
|
|
fcf038c4d8 | ||
|
|
46aad76039 | ||
|
|
9a654988f8 | ||
|
|
1ce749e93e | ||
|
|
db88432b9d | ||
|
|
354bcc715f | ||
|
|
5541198967 | ||
|
|
3e2dbe2c1b | ||
|
|
5d3d18f347 | ||
|
|
9a876f3d59 | ||
|
|
3b7600ae7b | ||
|
|
8bacf8ff93 | ||
|
|
b356e5d0a5 | ||
|
|
777516446d | ||
|
|
37c1d0f6a8 | ||
|
|
be1bf28cd8 | ||
|
|
de5f0fbb39 | ||
|
|
48fa2bd6de | ||
|
|
df584f34e9 | ||
|
|
0241ad1fe5 | ||
|
|
435ed212c6 | ||
|
|
1e9a6a1e14 | ||
|
|
a7ba3079cb | ||
|
|
97f1bb71c7 | ||
|
|
cad8613e81 | ||
|
|
b46e51f64d | ||
|
|
e6bf64548c | ||
|
|
5729e0cf3e | ||
|
|
b353539328 | ||
|
|
f2049ac7fa | ||
|
|
e82ba882d6 | ||
|
|
eecd7ed355 | ||
|
|
bc70b4e725 | ||
|
|
a52cebf0e5 | ||
|
|
4c31a7bcd6 | ||
|
|
cf938eca91 | ||
|
|
deef13ece9 | ||
|
|
a3d3ee24f8 | ||
|
|
6b37cc46a5 | ||
|
|
3132373c26 | ||
|
|
5b4ff1d01a | ||
|
|
20d8a42239 | ||
|
|
5d85dea5fe | ||
|
|
b19814dd2c | ||
|
|
ad39694026 | ||
|
|
a28f402f35 | ||
|
|
5596e3d2e5 | ||
|
|
00b9d277d2 | ||
|
|
f48c21bd4e | ||
|
|
9b23ec5953 | ||
|
|
b917592901 | ||
|
|
a1606b99d1 | ||
|
|
38ed141d0e | ||
|
|
64dde88b63 | ||
|
|
af628a72fa | ||
|
|
634e5e9624 | ||
|
|
6db71d7995 | ||
|
|
83378094b5 | ||
|
|
9afa40764e | ||
|
|
9899298500 | ||
|
|
ad58bc1d5f | ||
|
|
d76c9ce6db | ||
|
|
0606401d29 | ||
|
|
0e9556603e | ||
|
|
f4c0899e02 | ||
|
|
10ae4c5f92 | ||
|
|
eb967709dd | ||
|
|
b72b118299 | ||
|
|
556c04aca2 | ||
|
|
72ede1a109 | ||
|
|
4b89e92dd0 | ||
|
|
6b93125b5d | ||
|
|
4a3d9fd752 | ||
|
|
39c1e54cc9 | ||
|
|
cb3f1a5edc | ||
|
|
6112d6a780 | ||
|
|
b34a769773 | ||
|
|
e265d4b4d2 | ||
|
|
f95410a5b1 | ||
|
|
0eb56a8437 | ||
|
|
eb9f8021ae | ||
|
|
fdc3cb8f85 | ||
|
|
5fe86858ef | ||
|
|
0c9f9a60b7 | ||
|
|
4db135d5d0 | ||
|
|
8c5f7e62be | ||
|
|
ed011e93dc | ||
|
|
7546d8cad2 | ||
|
|
a98385c3a3 | ||
|
|
68639dfd55 | ||
|
|
d7f88c1890 | ||
|
|
357b7c58a1 | ||
|
|
f300573604 | ||
|
|
d65ddaec02 | ||
|
|
de44b1813e | ||
|
|
4070d47d0f | ||
|
|
11ef667433 | ||
|
|
e9feed59cd | ||
|
|
5e52036f7a | ||
|
|
d61c18a6ef | ||
|
|
5f4cf302a1 | ||
|
|
cb2b33eedb | ||
|
|
856a87f01c | ||
|
|
dbf6f6aa37 | ||
|
|
ad90d2e37a | ||
|
|
6223a1529f | ||
|
|
67e493b27f | ||
|
|
a74908214d | ||
|
|
6bd5327d50 | ||
|
|
94e398dc89 | ||
|
|
a5a19581fc | ||
|
|
ea409fc266 | ||
|
|
0e9d7106f7 | ||
|
|
6fe89fdc0e | ||
|
|
193e1374e1 | ||
|
|
e11a6ace80 | ||
|
|
9033bd8cec | ||
|
|
085551b5a7 | ||
|
|
cf81d58604 | ||
|
|
2560e5934f | ||
|
|
2a407b5928 | ||
|
|
019141ef3b | ||
|
|
44e9103942 | ||
|
|
a9e31aec8f | ||
|
|
901e1a73dc | ||
|
|
184ea915fc | ||
|
|
d58f96fb60 | ||
|
|
210a0a45c1 | ||
|
|
ee035681ab | ||
|
|
98598053c7 | ||
|
|
cc2d65d796 | ||
|
|
0e61b3dfd4 | ||
|
|
0a3035e5d5 | ||
|
|
8046c6837c | ||
|
|
30641a2df6 | ||
|
|
c82cd54b7b | ||
|
|
7adb1c0a6c | ||
|
|
cb55be1e5c | ||
|
|
d7a7144b33 | ||
|
|
e6a867a859 | ||
|
|
a9c8e4476f | ||
|
|
f914263b2f | ||
|
|
6f94d91afb | ||
|
|
57569e79fe | ||
|
|
b695db80ab | ||
|
|
ae1a43b8bf | ||
|
|
d9d9acfa3e | ||
|
|
d430936ae8 | ||
|
|
b02acc579f | ||
|
|
e87be4b9af | ||
|
|
399148aa59 | ||
|
|
abd733934b | ||
|
|
661e1f92d5 | ||
|
|
97b36681dc | ||
|
|
5dd5756a41 | ||
|
|
d2cfa374bd | ||
|
|
fb64c8aa4c | ||
|
|
86b54e2241 | ||
|
|
c33396ea60 | ||
|
|
24f48b5054 | ||
|
|
094c92c8b6 | ||
|
|
c2922a0cb2 | ||
|
|
9a357a716d | ||
|
|
5574fd748e | ||
|
|
f2be25667f | ||
|
|
053d0cfcaa | ||
|
|
0ff8184f47 | ||
|
|
f971835ae7 | ||
|
|
694e3ca121 | ||
|
|
be850c27c6 | ||
|
|
3aaf11ba1d | ||
|
|
3019d858c9 | ||
|
|
81e926ef2d | ||
|
|
016d646971 | ||
|
|
1f100566ad | ||
|
|
d69595e744 | ||
|
|
7a91e714fe | ||
|
|
e352de8b9c | ||
|
|
e36831b599 | ||
|
|
df74c7d54d | ||
|
|
d7986b5c7c | ||
|
|
3e888876d1 | ||
|
|
6af494206e | ||
|
|
93f32de8c1 | ||
|
|
e0b3e6464e | ||
|
|
c67d2f0836 | ||
|
|
49106a3dd9 | ||
|
|
b61c75dc84 | ||
|
|
465fc7c7d3 | ||
|
|
487e4ef559 | ||
|
|
34bf4f6bba | ||
|
|
af36978ede | ||
|
|
a375dfdc2e | ||
|
|
a5b0e66e9d | ||
|
|
2b3b7a5e2a | ||
|
|
b1dc9272a0 | ||
|
|
7c33c160b2 | ||
|
|
be232efbc6 | ||
|
|
bbac09b4a6 | ||
|
|
a4626ae7a1 | ||
|
|
e8551df8b9 | ||
|
|
77cb40aef4 | ||
|
|
827090fb24 | ||
|
|
39b9670fcc | ||
|
|
d227a2bf20 | ||
|
|
0b766415a4 | ||
|
|
3b727549d5 | ||
|
|
dbdf19ee8d | ||
|
|
2e3d621077 | ||
|
|
0455481f3d | ||
|
|
c9a7918397 | ||
|
|
957f0bc041 | ||
|
|
24f5a5813a | ||
|
|
860b2895f6 | ||
|
|
a510320d0e | ||
|
|
de04f18cb2 | ||
|
|
b84b73fda2 | ||
|
|
12b0618149 | ||
|
|
28a43393f9 | ||
|
|
21b3bdf204 | ||
|
|
3436fb7fb1 | ||
|
|
045b40b2b2 | ||
|
|
c09922d1a3 | ||
|
|
3e9032588e | ||
|
|
14e570b676 | ||
|
|
a412aa4886 | ||
|
|
a7a162d375 | ||
|
|
b444bdc5ec | ||
|
|
f9299e7950 | ||
|
|
8a9f73ada5 | ||
|
|
d5c1970ca2 | ||
|
|
f8aff3e12b | ||
|
|
490759243b | ||
|
|
e8a9e509a8 | ||
|
|
c392ad5dcb | ||
|
|
9bb084d610 | ||
|
|
00b05e2d7c | ||
|
|
93736fe57a | ||
|
|
995ae50418 | ||
|
|
42e6dff0f5 | ||
|
|
150513d3e5 | ||
|
|
13ed1e76bc | ||
|
|
a7d0e2eef5 | ||
|
|
134314c285 | ||
|
|
eb3e3db8e6 | ||
|
|
3b9b886ae9 | ||
|
|
d88df36c3d | ||
|
|
62d7311780 | ||
|
|
9d76ccc173 | ||
|
|
e8f7f9ad62 | ||
|
|
a3330705b3 | ||
|
|
d8b40c1a55 | ||
|
|
b5113dd420 | ||
|
|
a41b7f5535 | ||
|
|
8304bca71c | ||
|
|
6dfcc58144 | ||
|
|
9528b4ce8d | ||
|
|
73928092c4 | ||
|
|
79d3c7f9a2 | ||
|
|
108d9f84fc | ||
|
|
0854fc3493 | ||
|
|
86967175f7 | ||
|
|
a6b29525d2 | ||
|
|
d6c3cf3b8f | ||
|
|
ec502f3f4e | ||
|
|
a7a23e9e97 | ||
|
|
492b959204 | ||
|
|
dde67479be | ||
|
|
a6c163a705 | ||
|
|
159dd57f0c | ||
|
|
0cff1642c5 | ||
|
|
ee55002bf1 | ||
|
|
05b4350496 | ||
|
|
b68150a007 | ||
|
|
ba76c85824 | ||
|
|
5b291de28f | ||
|
|
5e84774737 | ||
|
|
773419b25f | ||
|
|
09668c2f67 | ||
|
|
072e5b492b | ||
|
|
1d19d69db9 | ||
|
|
a88b43dd11 | ||
|
|
a694bf9b6c | ||
|
|
bfcfa691a2 | ||
|
|
2703c6a33a | ||
|
|
3ad94f362a | ||
|
|
8508ca74f2 | ||
|
|
1752192688 | ||
|
|
eed12c2089 | ||
|
|
889aed6024 | ||
|
|
ac7840cbfd | ||
|
|
1f1304ca5b | ||
|
|
94098d8a07 | ||
|
|
4b9c5fcb73 | ||
|
|
6ed16e5e02 | ||
|
|
82a8e504af | ||
|
|
cc1660d9af | ||
|
|
52b6fc108b | ||
|
|
8b3b20aa93 | ||
|
|
408b0bfe2d | ||
|
|
655be76428 | ||
|
|
9fedf8d6b7 | ||
|
|
7c4e775872 | ||
|
|
ecfa640c28 | ||
|
|
1997eb20f3 | ||
|
|
726425bbb6 | ||
|
|
c068f26604 | ||
|
|
e60c2b1192 | ||
|
|
beb049f817 | ||
|
|
7021c9fe02 | ||
|
|
c2d5dece9e | ||
|
|
b76d41b84d | ||
|
|
3b9c76649d | ||
|
|
62ed8b6975 | ||
|
|
2422dba4d4 | ||
|
|
eb1916e3a3 | ||
|
|
df0d9137a6 | ||
|
|
fc36b87965 | ||
|
|
63d90ea275 | ||
|
|
bb7f8a5eef | ||
|
|
2f5a02ec43 | ||
|
|
d411d91966 | ||
|
|
54b712953a | ||
|
|
27ad020adc | ||
|
|
ce112bd4d7 | ||
|
|
2ffb73d053 | ||
|
|
6478d438b5 | ||
|
|
d87069eb4c | ||
|
|
5447350ab1 | ||
|
|
ea1a2960bf | ||
|
|
832f2c451e | ||
|
|
715c7c23a7 | ||
|
|
e9c2e40a43 | ||
|
|
603c275c09 | ||
|
|
7b9d6df2fd | ||
|
|
a4afecef3d | ||
|
|
4d9bfb89ae | ||
|
|
f2a74bd35e | ||
|
|
873ee3ac14 | ||
|
|
cfce2d00f5 | ||
|
|
8c65a21b86 | ||
|
|
a07e8477fb | ||
|
|
8b489e9ced | ||
|
|
77e2bb1d46 | ||
|
|
4ce24e080a | ||
|
|
4e5ca3dca6 | ||
|
|
2ed155ab47 | ||
|
|
65e71e3caf | ||
|
|
ee5efbcfcc | ||
|
|
6cf4530f7d | ||
|
|
e6ee09ca30 | ||
|
|
6d2f53b86c | ||
|
|
6500748c5a | ||
|
|
120dbeb4fc | ||
|
|
c42807487b | ||
|
|
e707e24da9 | ||
|
|
af817ec439 | ||
|
|
ddb44d8fd7 | ||
|
|
778822b12d | ||
|
|
9599ec3236 |
9
.github/workflows/mobile-daily-internal.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.32.8"
|
||||
RUST_VERSION: "1.85.1"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -38,6 +39,14 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install Rust ${{ env.RUST_VERSION }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Install Flutter Rust Bridge
|
||||
run: cargo install flutter_rust_bridge_codegen
|
||||
|
||||
- name: Increment version code for build
|
||||
run: |
|
||||
CURRENT_VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //')
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
name: "Internal release (photos with rust)"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.32.8"
|
||||
RUST_VERSION: "1.85.1"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mobile/apps/photos
|
||||
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install Rust ${{ env.RUST_VERSION }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Install Flutter Rust Bridge
|
||||
run: cargo install flutter_rust_bridge_codegen
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: "keystore/ente_photos_key.jks"
|
||||
encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }}
|
||||
|
||||
- name: Build PlayStore AAB
|
||||
run: |
|
||||
flutter build appbundle --dart-define=cronetHttpNoPlay=true --release --flavor playstore
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD_PHOTOS }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }}
|
||||
|
||||
- name: Upload AAB to PlayStore
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: io.ente.photos
|
||||
releaseFiles: mobile/apps/photos/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
track: internal
|
||||
|
||||
- name: Notify Discord
|
||||
uses: sarisia/actions-status-discord@v1
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_INTERNAL_RELEASE_WEBHOOK }}
|
||||
nodetail: true
|
||||
title: "🏆 Internal release available for Photos"
|
||||
description: "[Download](https://play.google.com/store/apps/details?id=io.ente.photos)"
|
||||
color: 0x00ff00
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.32.8"
|
||||
RUST_VERSION: "1.85.1"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -35,6 +36,14 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install Rust ${{ env.RUST_VERSION }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Install Flutter Rust Bridge
|
||||
run: cargo install flutter_rust_bridge_codegen
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
|
||||
@@ -142,6 +142,22 @@ var _updateFreeUserStorage = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var _sendMail = &cobra.Command{
|
||||
Use: "send-mail <to-email> <from-email> <from-name>",
|
||||
Args: cobra.ExactArgs(3),
|
||||
Short: "Sends a test mail via the admin api",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
})
|
||||
return ctrl.SendTestMail(context.Background(), *flags, args[0], args[1], args[2])
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(_adminCmd)
|
||||
_ = _userDetailsCmd.MarkFlagRequired("admin-user")
|
||||
@@ -159,5 +175,6 @@ func init() {
|
||||
_updateFreeUserStorage.Flags().StringP("user", "u", "", "The email of the user to update subscription for. (required)")
|
||||
// add a flag with no value --no-limit
|
||||
_updateFreeUserStorage.Flags().String("no-limit", "True", "When true, sets 100TB as storage limit, and expiry to current date + 100 years")
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _disablePasskeyCmd, _updateFreeUserStorage, _listUsers, _deleteUser)
|
||||
_sendMail.Flags().StringP("admin-user", "a", "", "The email of the admin user. ")
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _disablePasskeyCmd, _updateFreeUserStorage, _listUsers, _deleteUser, _sendMail)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/pkg"
|
||||
"github.com/spf13/cobra/doc"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/ente-io/cli/pkg"
|
||||
"github.com/spf13/cobra/doc"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -20,11 +21,6 @@ var ctrl *pkg.ClICtrl
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "ente",
|
||||
Short: "CLI tool for exporting your photos from ente.io",
|
||||
// Uncomment the following line if your bare application
|
||||
// has an action associated with it:
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Sprintf("Hello World")
|
||||
},
|
||||
}
|
||||
|
||||
func GenerateDocs() error {
|
||||
|
||||
@@ -139,5 +139,28 @@ func (c *Client) UpdateFreePlanSub(ctx context.Context, userDetails *models.User
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) SendTestMail(ctx context.Context, toEmail, fromEmail, fromName string) error {
|
||||
body := map[string]interface{}{
|
||||
"to": []string{toEmail},
|
||||
"fromName": fromName,
|
||||
"fromEmail": fromEmail,
|
||||
"subject": "Test mail from Ente",
|
||||
"body": "This is a test mail from Ente",
|
||||
}
|
||||
r, err := c.restClient.R().
|
||||
SetContext(ctx).
|
||||
SetBody(body).
|
||||
Post("/admin/mail")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.IsError() {
|
||||
return &ApiError{
|
||||
StatusCode: r.StatusCode(),
|
||||
Message: r.String(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,6 +156,23 @@ func (c *ClICtrl) UpdateFreeStorage(ctx context.Context, params model.AdminActio
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) SendTestMail(ctx context.Context, params model.AdminActionForUser, to, from, fromName string) error {
|
||||
accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.SendTestMail(accountCtx, to, from, fromName)
|
||||
if err != nil {
|
||||
if apiErr, ok := err.(*api.ApiError); ok && apiErr.StatusCode == 400 && strings.Contains(apiErr.Message, "Token is too old") {
|
||||
fmt.Printf("Error: old admin token, please re-authenticate using `ente account add` \n")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Successfully sent test email to %s\n", to)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) buildAdminContext(ctx context.Context, adminEmail string) (context.Context, error) {
|
||||
accounts, err := c.GetAccounts(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -26,6 +26,10 @@ export const sidebar = [
|
||||
text: "Collecting photos",
|
||||
link: "/photos/features/collect",
|
||||
},
|
||||
{
|
||||
text: "Custom domains",
|
||||
link: "/photos/features/custom-domains/",
|
||||
},
|
||||
{
|
||||
text: "Deduplicate",
|
||||
link: "/photos/features/deduplicate",
|
||||
|
||||
0
docs/docs/cli/index.md
Normal file
@@ -35,4 +35,4 @@ be specific to your distro (e.g. `xdg-desktop-menu forceupdate`).
|
||||
> [!NOTE]
|
||||
>
|
||||
> If you're using an AppImage and not seeing the icon, you'll need to
|
||||
> [enable AppImage desktop integration](/photos/troubleshooting/desktop-install/#appimage-desktop-integration).
|
||||
> [enable AppImage desktop integration](/photos/troubleshooting/desktop-install/#appimage-desktop-integration).
|
||||
|
||||
BIN
docs/docs/photos/features/custom-domains/cf.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
105
docs/docs/photos/features/custom-domains/index.md
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Custom domains
|
||||
description: Use your own domain when sharing photos and videos stored in Ente Photos
|
||||
---
|
||||
|
||||
# Custom domains
|
||||
|
||||
Custom domains allow you to serve your public links with your own personalized domain.
|
||||
|
||||
For example, if I have an Ente album and wish to share it with my friends, I can go to the album's sharing settings and create a public link. When I copy this link, it will of the form of
|
||||
|
||||
```
|
||||
https://albums.ente.io/?t=...
|
||||
```
|
||||
|
||||
The custom domains feature allows you to instead create a link that uses your own domain, say
|
||||
|
||||
```
|
||||
https://pics.example.org/?t=...
|
||||
```
|
||||
|
||||
You don't need to run any servers or manage any services, Ente will still host and serve your album for you, the only thing that changes is that you can serve your links using your personalized domain.
|
||||
|
||||
## Availability
|
||||
|
||||
The custom domains feature requires the ability to publicly share albums which for abuse prevention reasons can only be done by people with an active Ente subscription.
|
||||
|
||||
## Setup
|
||||
|
||||
The setup involves two steps:
|
||||
|
||||
1. Letting Ente know about the domain you wish to use for serving your public links
|
||||
2. Updating your DNS settings to point your domain (or subdomain) to **my.ente.io**
|
||||
|
||||
For people who are comfortable with changing DNS settings on their domain provider, this entire process is very simple will take a minute. For people who are not comfortable with changing DNS, we will provide a more detailed breakdown below.
|
||||
|
||||
Let's dive in.
|
||||
|
||||
To make the process concrete, let's assume we're trying to use _pics.example.org_ as our custom domain. Note that there is no restriction to use a subdomain, a top level domain can be used as a custom domain too. That is, either of _example.org_ or _subdomain.example.org_ is fine, Ente will work with both.
|
||||
|
||||
### Step 1 - Link your domain
|
||||
|
||||
The first step is to let Ente know about the domain or subdomain you wish to use by linking it to your account.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Currently (Aug 2025) the ability to link a custom domain is only present in Ente's web app, [web.ente.io](https://web.ente.io). It will come to Ente mobile and desktop when their next versions get released.
|
||||
|
||||
Head over to Preferences > Custom domains, in the domain field enter "pics.example.org" (replace with your subdomain) and press "Save". That's it. The linking is done.
|
||||
|
||||
### Step 2 - Add DNS entry
|
||||
|
||||
The second step is to add a CNAME entry in your DNS provider that forwards requests for pics.example.org (replace with your subdomain) to **my.ente.io**.
|
||||
|
||||
Specifically, you need to add a `CNAME record` from the domain (or subdomain) of your choice to `my.ente.io`. You can leave the `TTL` at its default.
|
||||
|
||||
| Record Type | Name | Value | TTL |
|
||||
| ----------- | :------------------------: | -----------: | -------------- |
|
||||
| CNAME | Your subdomain, e.g `pics` | `my.ente.io` | Auto (default) |
|
||||
|
||||
The exact steps for doing this depend on the DNS provider that you're using.
|
||||
|
||||
> Your DNS provider usually is the service from which you bought your domain. The domain name seller will provide some sort of an admin panel where you can configure your DNS settings.
|
||||
|
||||
As concrete examples, here is how this step would look for Cloudflare:
|
||||
|
||||

|
||||
|
||||
Note that orange proxy option is off. And here is how it would look for Namecheap:
|
||||
|
||||

|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> The examples are using "pics" as the subdomain, but that's just an example, you can use anything you like (or use "@" if you'd like to use the root domain itself).
|
||||
|
||||
The time it takes for DNS records to update is dependent on your DNS provider. Usually the changes should start reflecting within a few minutes, and should almost always reflect within an hour.
|
||||
|
||||
Once the DNS changes have been applied, then you can take any public link to your shared albums, replace `albums.ente.io` with your choice (e.g. `pics.example.org`), and the link will still work.
|
||||
|
||||
You don't need to do this manually though, the apps will do it for you. More on this in the next section. But first, some troubleshooting tips.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If your domain is not working, go through the following checklist.
|
||||
|
||||
- The CNAME should be from your domain to my.ente.io, not the other way around. That is, `pics.example.org => my.ente.io`.
|
||||
|
||||
- If you're using Cloudflare DNS, make sure that the "Orange" proxy status toggle is off, and the Proxy status is the "Grey" DNS only.
|
||||
|
||||
## Using
|
||||
|
||||
Using is trivial. When you go to an album's sharing options and copy the link to it, Ente will automatically copy the link that uses your configured domain.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Currently (Aug 2025) the ability to automatically substitute your custom domain is only present in Ente's web app, [web.ente.io](https://web.ente.io). It will come to Ente mobile and desktop when their next versions get released.
|
||||
|
||||
## Unsetting
|
||||
|
||||
To stop using your custom domain, we need to undo the two steps we did during setup.
|
||||
|
||||
1. Unlink your domain in Ente. This can be done just by going to Preferences > Custom Domains, clearing the value in the "Domain" input and pressing "Update".
|
||||
|
||||
2. Remove the CNAME record you added during setup in your DNS provider.
|
||||
BIN
docs/docs/photos/features/custom-domains/nc.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
@@ -112,4 +112,4 @@ ip addr add 10.10.10.1/24 dev dummy0
|
||||
ip link set dummy0 up
|
||||
```
|
||||
|
||||
Once the interface is up, Ente correctly detects that the system is online.
|
||||
Once the interface is up, Ente correctly detects that the system is online.
|
||||
|
||||
@@ -63,11 +63,20 @@ It has no relation to Backblaze, Wasabi or Scaleway.
|
||||
Each bucket's endpoint, region, key and secret should be configured accordingly
|
||||
if using an external bucket.
|
||||
|
||||
A sample configuration for `b2-eu-cen` is provided, which can be used for other
|
||||
2 buckets as well:
|
||||
If a bucket has SSL support enabled, set `s3.are_local_buckets` to `false`. Enable path-style URL by setting `s3.use_path_style_urls` to `true`.
|
||||
|
||||
::: note
|
||||
|
||||
You can configure this for individual buckets over defining top-level configuration if you are using the latest server image (August 2025)
|
||||
|
||||
:::
|
||||
|
||||
A sample configuration for `b2-eu-cen` is provided, which can be used for other 2 buckets as well:
|
||||
|
||||
```yaml
|
||||
b2-eu-cen:
|
||||
are_local_buckets: true
|
||||
use_path_style_urls: true
|
||||
key: <key>
|
||||
secret: <secret>
|
||||
endpoint: localhost:3200
|
||||
|
||||
@@ -96,8 +96,8 @@ provide correct credentials for proper connectivity within Museum.
|
||||
The `s3` section within `museum.yaml` is by default configured to use local
|
||||
MinIO buckets when using `quickstart.sh` or Docker Compose.
|
||||
|
||||
If you wish to use an external S3 provider, you can edit the configuration with
|
||||
your provider's credentials, and set `s3.are_local_buckets` to `false`.
|
||||
If you wish to use an external S3 provider with SSL, you can edit the configuration with
|
||||
your provider's credentials, and set `s3.are_local_buckets` to `false`. Additionally, you can configure this for specific buckets in the corresponding bucket sections in the Compose file.
|
||||
|
||||
If you are using default MinIO, it is accessible at port `3200`. Web Console can
|
||||
be accessed by enabling port `3201` in the Compose file.
|
||||
@@ -111,11 +111,11 @@ and [troubleshooting](/self-hosting/troubleshooting/uploads) sections.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| -------------------------------------- | -------------------------------------------- | ------- |
|
||||
| `s3.b2-eu-cen` | Primary hot storage S3 config | |
|
||||
| `s3.b2-eu-cen` | Primary hot storage bucket configuration | |
|
||||
| `s3.wasabi-eu-central-2-v3.compliance` | Whether to disable compliance lock on delete | `true` |
|
||||
| `s3.scw-eu-fr-v3` | Optional secondary S3 config | |
|
||||
| `s3.wasabi-eu-central-2-derived` | Derived data storage | |
|
||||
| `s3.are_local_buckets` | Use local MinIO-compatible storage | `false` |
|
||||
| `s3.scw-eu-fr-v3` | Cold storage bucket configuration | |
|
||||
| `s3.wasabi-eu-central-2-v3` | Secondary hot storage configuration | |
|
||||
| `s3.are_local_buckets` | | `true` |
|
||||
| `s3.use_path_style_urls` | Enable path-style URLs for MinIO | `false` |
|
||||
|
||||
### Encryption Keys
|
||||
@@ -171,6 +171,8 @@ smtp:
|
||||
email:
|
||||
# Optional name for sender
|
||||
sender-name:
|
||||
# Optional encryption
|
||||
encryption:
|
||||
```
|
||||
|
||||
| Variable | Description | Default |
|
||||
@@ -181,6 +183,7 @@ smtp:
|
||||
| `smtp.password` | SMTP auth password | |
|
||||
| `smtp.email` | Sender email address | |
|
||||
| `smtp.sender-name` | Custom name for email sender | |
|
||||
| `smtp.encryption` | Encryption method (tls, ssl) | |
|
||||
| `transmail.key` | Zeptomail API key | |
|
||||
|
||||
### WebAuthn Passkey Support
|
||||
|
||||
45
mobile/.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
melos_*.iml
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
72
mobile/analysis_options.yaml
Normal file
@@ -0,0 +1,72 @@
|
||||
# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html
|
||||
# or https://pub.dev/packages/lint (Effective dart)
|
||||
# use "flutter analyze ." or "dart analyze ." for running lint checks
|
||||
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
linter:
|
||||
rules:
|
||||
# Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml
|
||||
# Ref https://dart-lang.github.io/linter/lints/
|
||||
- avoid_print
|
||||
- avoid_unnecessary_containers
|
||||
- avoid_web_libraries_in_flutter
|
||||
- no_logic_in_create_state
|
||||
- prefer_const_constructors
|
||||
- prefer_const_constructors_in_immutables
|
||||
- prefer_const_declarations
|
||||
- prefer_const_literals_to_create_immutables
|
||||
- prefer_final_locals
|
||||
- require_trailing_commas
|
||||
- sized_box_for_whitespace
|
||||
- use_full_hex_values_for_flutter_colors
|
||||
- use_key_in_widget_constructors
|
||||
- cancel_subscriptions
|
||||
|
||||
|
||||
- avoid_empty_else
|
||||
- exhaustive_cases
|
||||
|
||||
# just style suggestions
|
||||
- sort_pub_dependencies
|
||||
- use_rethrow_when_possible
|
||||
- prefer_double_quotes
|
||||
- directives_ordering
|
||||
- always_use_package_imports
|
||||
- sort_child_properties_last
|
||||
- unawaited_futures
|
||||
|
||||
analyzer:
|
||||
errors:
|
||||
avoid_empty_else: error
|
||||
exhaustive_cases: error
|
||||
curly_braces_in_flow_control_structures: error
|
||||
directives_ordering: error
|
||||
require_trailing_commas: error
|
||||
always_use_package_imports: warning
|
||||
prefer_final_fields: error
|
||||
unused_import: error
|
||||
camel_case_types: error
|
||||
prefer_is_empty: warning
|
||||
use_rethrow_when_possible: info
|
||||
unused_field: warning
|
||||
use_key_in_widget_constructors: warning
|
||||
sort_child_properties_last: warning
|
||||
sort_pub_dependencies: warning
|
||||
library_private_types_in_public_api: warning
|
||||
constant_identifier_names: ignore
|
||||
prefer_const_constructors: warning
|
||||
prefer_const_declarations: warning
|
||||
prefer_const_constructors_in_immutables: warning
|
||||
prefer_final_locals: warning
|
||||
unnecessary_const: error
|
||||
cancel_subscriptions: error
|
||||
unrelated_type_equality_checks: error
|
||||
unnecessary_cast: info
|
||||
|
||||
|
||||
unawaited_futures: warning # convert to warning after fixing existing issues
|
||||
invalid_dependency: info
|
||||
use_build_context_synchronously: ignore # experimental lint, requires many changes
|
||||
prefer_interpolation_to_compose_strings: ignore # later too many warnings
|
||||
prefer_double_quotes: ignore # too many warnings
|
||||
avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides
|
||||
@@ -30,10 +30,13 @@ if (keystorePropertiesFile.exists()) {
|
||||
|
||||
android {
|
||||
namespace "io.ente.auth"
|
||||
compileSdk 34
|
||||
compileSdk 36
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
// Flag to enable support for the new language APIs
|
||||
coreLibraryDesugaringEnabled true
|
||||
// Sets Java compatibility to Java 8
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
@@ -56,8 +59,8 @@ android {
|
||||
applicationId "io.ente.auth"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 35
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -115,4 +118,7 @@ flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {}
|
||||
dependencies {
|
||||
// For AGP 7.4+
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
}
|
||||
|
||||
6
mobile/apps/auth/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Please add these rules to your existing keep rules in order to suppress warnings.
|
||||
# This is generated automatically by the Android Gradle plugin.
|
||||
-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue
|
||||
-dontwarn com.google.errorprone.annotations.CheckReturnValue
|
||||
-dontwarn com.google.errorprone.annotations.Immutable
|
||||
-dontwarn com.google.errorprone.annotations.RestrictedApi
|
||||
@@ -6,6 +6,19 @@ allprojects {
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
|
||||
subprojects {
|
||||
afterEvaluate { project ->
|
||||
if (project.hasProperty('android')) {
|
||||
project.android {
|
||||
if (namespace == null) {
|
||||
namespace project.group
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
|
||||
@@ -19,8 +19,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
id "com.android.application" version "8.6.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
||||
@@ -382,6 +382,11 @@
|
||||
{
|
||||
"title": "CoinDCX"
|
||||
},
|
||||
{
|
||||
"title": "CoinTracking",
|
||||
"slug": "cointracking",
|
||||
"altNames": ["cointracking.info", "Coin Tracking"]
|
||||
},
|
||||
{
|
||||
"title": "colorado",
|
||||
"altNames": [
|
||||
@@ -735,6 +740,11 @@
|
||||
{
|
||||
"title": "Hivelocity"
|
||||
},
|
||||
{
|
||||
"title": "HRDocumentBox",
|
||||
"slug": "hrdocumentbox",
|
||||
"altNames": ["HRDocumentBox", "HR Document Box"]
|
||||
},
|
||||
{
|
||||
"title": "HSA Bank",
|
||||
"slug": "hsa_bank",
|
||||
@@ -1040,6 +1050,13 @@
|
||||
"MistralAI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Mobile01",
|
||||
"slug": "mobile01",
|
||||
"altNames": [
|
||||
"M01"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Mozilla"
|
||||
},
|
||||
@@ -1358,6 +1375,13 @@
|
||||
"r10.net"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "RaiderIO",
|
||||
"slug": "raider_io",
|
||||
"altNames": [
|
||||
"raider.io"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Raindrop.io",
|
||||
"slug": "raindrop_io",
|
||||
@@ -1501,6 +1525,10 @@
|
||||
{
|
||||
"title": "Skinport"
|
||||
},
|
||||
{
|
||||
"title": "Skyscanner",
|
||||
"hex": "0770E3"
|
||||
},
|
||||
{
|
||||
"title": "SMSPool",
|
||||
"slug": "sms_pool_net",
|
||||
@@ -1762,6 +1790,11 @@
|
||||
"uollet.com.br"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "VHV",
|
||||
"slug": "vhv",
|
||||
"altNames": ["VHV", "VHV Versicherung"]
|
||||
},
|
||||
{
|
||||
"title": "Vikunja"
|
||||
},
|
||||
|
||||
27
mobile/apps/auth/assets/custom-icons/icons/cointracking.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1220.8 227.9" style="enable-background:new 0 0 1220.8 227.9;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0D253E;}
|
||||
.st1{fill:#008AFB;}
|
||||
</style>
|
||||
<title>CoinTracking light</title>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Layer_1-2">
|
||||
<path class="st0" d="M198.1,167c-30.2,0-54.7-24.5-54.7-54.7s24.5-54.7,54.7-54.7s54.7,24.5,54.7,54.7l0,0 C252.8,142.5,228.3,167,198.1,167z M198.1,81.6c-17,0-30.7,13.7-30.7,30.7s13.7,30.7,30.7,30.7s30.7-13.7,30.7-30.7l0,0 C228.8,95.4,215,81.6,198.1,81.6z"/>
|
||||
<path class="st0" d="M292.2,59.5h-23.6v107.9h23.6V59.5z"/>
|
||||
<path class="st0" d="M339.5,167.4h-23.8V59.5h23.8v16.2c6.2-12.5,21-18.5,33-18.5c26.1,0,41.1,16.9,41.1,47.3v62.8h-23.8v-60.1 c0-17.1-8.8-26.8-22.6-26.8c-14.1,0-27.7,7.6-27.7,28.9V167.4z"/>
|
||||
<path class="st1" d="M390.6,8.2h151.7v22.9h-65.9v136.3h-25V31.1h-60.9V8.2H390.6z"/>
|
||||
<path class="st1" d="M540.3,167.4h-23.8V59.5h23.8V79c7.4-13.5,15.4-19.5,29.6-21.8c7.5-1.2,15.7,1.8,19.2,4.2l-3.9,22 c-4.9-2.5-10.4-3.8-15.9-3.7c-20.3,0-28.9,20.3-28.9,49L540.3,167.4L540.3,167.4z"/>
|
||||
<path class="st1" d="M680.7,151.9c-7.2,11.8-22.9,17.8-36.3,17.8c-29.1,0-54.8-21.9-54.8-56.4s25.6-56.1,54.8-56.1 c12.9,0,28.9,5.3,36.3,17.5V59.5h23.6v107.9h-23.6V151.9z M647.2,146.6c17.6,0,33.3-12.2,33.3-33.5s-17.1-32.8-33.3-32.8 c-18,0-33,12.9-33,32.8S629.2,146.6,647.2,146.6L647.2,146.6z"/>
|
||||
<path class="st1" d="M778.4,57.2c17.1,0,32.6,6.7,42.7,18.7l-18.5,14.8c-5.8-6.7-14.8-10.4-24.3-10.4c-18,0-34,12.7-34,32.8 s15.9,33.7,34,33.7c9.5,0,18.5-3.9,24.3-10.6l18.7,14.6c-10.2,12-25.6,18.9-43,18.9c-31,0-57.5-22.4-57.5-56.6 S747.4,57.2,778.4,57.2z"/>
|
||||
<path class="st1" d="M860.3,117.2v50.1h-23.6V0.8h23.6V95l34.2-35.6h31.9l-46,46.9l56.4,61h-30.5L860.3,117.2z"/>
|
||||
<path class="st1" d="M967.4,59.5h-23.6v107.9h23.6V59.5z"/>
|
||||
<path class="st1" d="M1014.7,167.4h-23.8V59.5h23.8v16.2c6.2-12.5,21-18.5,33-18.5c26.1,0,41.1,16.9,41.1,47.3v62.8H1065v-60.1 c0-17.1-8.8-26.8-22.6-26.8c-14.1,0-27.7,7.6-27.7,28.9V167.4z"/>
|
||||
<path class="st1" d="M1220.2,111.5c0-12.3-4.5-24.2-12.5-33.6l13-17.1l-19.6-13l-11.9,15.7c-8.3-4.1-17.5-6.2-26.8-6.2 c-31.9,0-57.8,24.4-57.8,54.3s25.9,54.3,57.8,54.3c20.2,0,34.2,10.2,34.2,19.3s-14.1,19.3-34.2,19.3s-34.2-10.2-34.2-19.3h-23.6 c0,24,25.4,42.9,57.8,42.9s57.8-18.8,57.8-42.9c0-13.2-7.7-24.8-19.9-32.6C1212.5,142.5,1220.2,127.8,1220.2,111.5z M1128.2,111.5 c0-16.9,15.4-30.7,34.2-30.7s34.2,13.8,34.2,30.7s-15.4,30.7-34.2,30.7S1128.2,128.4,1128.2,111.5L1128.2,111.5z"/>
|
||||
<path class="st0" d="M81.4,170.5C36.4,170.5,0,134,0,89c0-21.6,8.6-42.3,23.9-57.6c31.8-31.8,83.4-31.8,115.2,0l0,0l-17.7,17.7 C99.3,27,63.6,27,41.5,49.1s-22.1,57.8,0,79.9s57.8,22.1,79.9,0l17.7,17.7C123.8,162,103.1,170.6,81.4,170.5z"/>
|
||||
<circle class="st0" cx="280.7" cy="15.5" r="15.5"/>
|
||||
<circle class="st1" cx="954.7" cy="15.5" r="15.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 18 KiB |
26
mobile/apps/auth/assets/custom-icons/icons/mobile01.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="320" height="280" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
|
||||
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="Layer1000">
|
||||
<g id="Layer1002">
|
||||
<g id="Layer1003">
|
||||
<path d="m123.08,34.29c-66.43,0 -120.27,53.85 -120.27,120.27c0,66.43 53.85,120.27 120.27,120.27c66.47,0 120.32,-53.85 120.32,-120.27c0,-66.43 -53.85,-120.27 -120.32,-120.27zm0,215.67c-52.67,0 -95.36,-42.73 -95.36,-95.4c0,-52.67 42.68,-95.4 95.36,-95.4c52.72,0 95.4,42.73 95.4,95.4c0,52.67 -42.68,95.4 -95.4,95.4z" fill="#2a5e00" fill-rule="evenodd" id="path7"/>
|
||||
<g id="Layer1004">
|
||||
<g id="Layer1005">
|
||||
<path d="m138.72,146.29l59.61,-41.47l7.78,33.7l-67.39,7.78z" fill="#2a5e00" fill-rule="evenodd" id="path8"/>
|
||||
<path d="m110.88,146.29l-59.61,-41.47l-7.78,33.7l67.39,7.78z" fill="#2a5e00" fill-rule="evenodd" id="path9"/>
|
||||
</g>
|
||||
<path d="m43.95,192.02l74.62,49.75l87.12,-78.8l-161.75,29.05z" fill="#2a5e00" fill-rule="evenodd" id="path10"/>
|
||||
</g>
|
||||
<path d="m94.24,59.29l-30.48,-55.1l54.26,33.24l-23.79,21.86z" fill="#2a5e00" fill-rule="evenodd" id="path11"/>
|
||||
<path d="m202.64,78.1l30.43,-55.1l-54.22,33.24l23.79,21.86z" fill="#2a5e00" fill-rule="evenodd" id="path12"/>
|
||||
</g>
|
||||
<path d="m275.63,274.67l29.35,0l0,-240.76l-29.35,0l0,240.76z" fill="#2a5e00" fill-rule="evenodd" id="path13"/>
|
||||
<path d="m317.94,125.93c0,15.3 -12.33,27.63 -27.63,27.63c-15.26,0 -27.63,-12.33 -27.63,-27.63c0,-15.26 12.37,-27.63 27.63,-27.63c15.3,0 27.63,12.37 27.63,27.63z" fill="#2a5e00" fill-rule="evenodd" id="path14"/>
|
||||
<path d="m288.84,33.91l-41.76,0l16.76,45.99l23.58,0l1.42,-45.99z" fill="#2a5e00" fill-rule="evenodd" id="path15"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
17
mobile/apps/auth/assets/custom-icons/icons/raider_io.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="katman_1" xmlns="http://www.w3.org/2000/svg" baseProfile="tiny" version="1.2" viewBox="0 0 295.13 303">
|
||||
<!-- Generator: Adobe Illustrator 29.6.0, SVG Export Plug-In . SVG Version: 2.1.1 Build 207) -->
|
||||
<g id="Logo_2ColorWhite">
|
||||
<path d="M453,42.76h-59.41v218.34h32.58v-76h26.83c18.81,0,26.83,5.13,26.83,15.81v60.2h32.59v-60.21c0-18.45-8.75-27.41-15.75-31.17,7-3.85,15.75-10.31,15.75-28.75v-52.07c0-21.05-10.31-46.15-59.42-46.15ZM479.83,140.98c0,10.67-8,13.18-26.83,13.18h-26.83v-80.47h26.83c18.81,0,26.83,4.54,26.83,15.22v52.07Z" fill="#fff"/>
|
||||
<path d="M607.59,39.5c-39.84,0-62.73,18-62.73,49.41v172.19h32.58v-76h60.31v76h32.58V88.91c-.01-31.41-22.88-49.41-62.74-49.41ZM637.75,154.15h-60.3v-65.24c0-13,9-18.54,30.15-18.54s30.15,5.54,30.15,18.54v65.24Z" fill="#fff"/>
|
||||
<polygon points="702.87 73.69 716.14 73.69 716.14 230.17 702.87 230.17 702.87 261.1 761.99 261.1 761.99 230.17 748.72 230.17 748.72 73.69 761.99 73.69 761.99 42.76 702.87 42.76 702.87 73.69" fill="#fff"/>
|
||||
<path d="M857.66,42.76h-62.73v218.34h62.73c39.87,0,62.74-18,62.74-49.46v-119.42c0-31.42-22.87-49.46-62.74-49.46ZM887.8,211.64c0,13-9,18.53-30.14,18.53h-30.14V73.69h30.14c21.14,0,30.14,5.54,30.14,18.53v119.42Z" fill="#fff"/>
|
||||
<path d="M953.52,92.22v119.42c0,31.42,22.87,49.46,62.74,49.46h49.49v-30.93h-49.45c-21.14,0-30.16-5.54-30.16-18.53v-40.65h73v-30.93h-73v-47.84c0-13,9-18.53,30.16-18.53h49.45v-30.93h-49.45c-39.91,0-62.78,18.04-62.78,49.46Z" fill="#fff"/>
|
||||
<path d="M1158.23,42.81h-59.42v218.34h32.6v-76.36h26.82c18.81,0,26.84,4.55,26.84,15.22v61.14h32.58v-61.14c0-18.44-8.75-26.82-15.74-30.57,7-3.85,15.74-12.35,15.74-30.79v-49.69c0-21.05-10.32-46.15-59.42-46.15ZM1185.07,138.65c0,10.67-8,15.22-26.84,15.22h-26.82v-80.13h26.82c18.81,0,26.84,4.54,26.84,15.22v49.69Z" fill="#fff"/>
|
||||
<rect x="1250.28" y="201.98" width="32.58" height="59.12" fill="#fff"/>
|
||||
<polygon points="1315.82 73.69 1329.08 73.69 1329.08 230.17 1315.82 230.17 1315.82 261.1 1374.93 261.1 1374.93 230.17 1361.67 230.17 1361.67 73.69 1374.93 73.69 1374.93 42.76 1315.82 42.76 1315.82 73.69" fill="#fff"/>
|
||||
<path d="M1470.75,39.5c-39.85,0-62.72,18-62.72,49.46v126c0,31.43,22.87,49.45,62.72,49.45s62.74-18,62.74-49.45v-126c-.03-31.46-22.9-49.46-62.74-49.46ZM1500.9,215.01c0,13-9,18.53-30.15,18.53s-30.14-5.54-30.14-18.53v-126c0-13,9-18.54,30.14-18.54s30.15,5.54,30.15,18.54v126Z" fill="#fff"/>
|
||||
<path d="M292.75,118.28c1.83-2.01,2.65-4.74,2.23-7.43-.21-.81-.41-1.63-.63-2.44-.97-2.59-3.07-4.58-5.7-5.42l-28-8.45c-6.93-2.1-11.04-9.23-9.39-16.28l6.67-28.47c.56-2.32.21-4.77-1-6.83-.81-1.37-1.96-2.51-3.33-3.32-2.06-1.21-4.5-1.57-6.82-1l-28.47,6.67c-7.05,1.66-14.18-2.46-16.27-9.39l-8.46-28c-.83-2.57-2.76-4.64-5.28-5.63-.91-.25-1.83-.46-2.74-.7-2.63-.37-5.3.45-7.27,2.23l-21.32,20c-5.28,4.96-13.51,4.96-18.79,0L116.86,3.82c-1.97-1.78-4.64-2.6-7.27-2.23-.91.24-1.83.45-2.73.7-2.52.99-4.46,3.05-5.29,5.63l-8.46,28c-2.09,6.94-9.22,11.05-16.27,9.39l-28.47-6.71c-2.32-.56-4.77-.21-6.83,1-1.37.81-2.52,1.95-3.33,3.32-1.21,2.06-1.56,4.51-1,6.83l6.67,28.48c1.65,7.06-2.48,14.19-9.43,16.27l-28,8.45c-2.64.85-4.74,2.86-5.7,5.46-.22.81-.42,1.63-.63,2.44-.42,2.69.4,5.42,2.23,7.43l20,21.32c4.96,5.28,4.96,13.51,0,18.79l-20,21.32c-1.86,2.04-2.69,4.82-2.23,7.55.2.74.38,1.49.58,2.23.96,2.63,3.08,4.67,5.75,5.51l28,8.46c6.93,2.09,11.05,9.22,9.39,16.27l-6.67,28.47c-.6,2.56-.07,5.25,1.46,7.39,2.17,3.13,6.02,4.64,9.74,3.8l47.08-5.1c2.39-.24,4.14-2.36,3.93-4.76-1.57-17.27-9-81.84-11.73-105.94-.66-5.76,1.5-11.49,5.8-15.39l46.11-42c4.53-4.13,11.47-4.13,16,0l46.05,42c4.29,3.89,6.45,9.61,5.79,15.37-2.75,24.08-10.09,88.68-11.65,106-.21,2.39,1.54,4.51,3.93,4.75l47.09,5.1c2.58.61,5.29.06,7.43-1.49.9-.63,1.67-1.4,2.3-2.3,1.53-2.14,2.06-4.84,1.46-7.4l-6.67-28.47c-1.65-7.05,2.46-14.17,9.39-16.26l28-8.46c2.67-.84,4.79-2.88,5.75-5.51.2-.74.38-1.49.58-2.24.45-2.72-.37-5.5-2.23-7.54l-20-21.32c-4.96-5.28-4.96-13.51,0-18.79l19.97-21.36Z" fill="#e5a024"/>
|
||||
<path d="M152.13,125.06c-2.15-2.52-5.94-2.81-8.46-.66-.24.2-.46.42-.66.66l-15.81,18.18c-3.17,3.64-4.78,8.38-4.49,13.2l8,135.87c.33,4.92,4.32,8.8,9.25,9,2.5.13,5.02.19,7.56.2,2.54,0,5.07-.06,7.6-.2,4.93-.19,8.93-4.07,9.25-9l8.11-135.81c.28-4.83-1.34-9.57-4.51-13.22l-15.84-18.22Z" fill="#808080"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-376.452 18.166 399.284 251.868"><path d="M195.1 220c2.1 0 4.1-.5 6-1.6l21.9-12.6c4.4-2.5 9.5-3.6 14.6-3 26.6 3.1 45.2 8.1 50.7 9.7 1.1.3 2.4-.1 3.1-1 .9-1.1 2-2.9 2.9-5.5.8-2.5.9-4.6.8-6.1-.1-1.2-.9-2.3-2.1-2.6-8.6-2.5-46.7-12.8-97.9-12.8s-89.3 10.3-97.9 12.8c-1.2.3-2 1.4-2.1 2.6-.1 1.4 0 3.5.8 6.1.8 2.6 2 4.4 2.9 5.5.7.9 2 1.3 3.1 1 5.5-1.6 24.2-6.6 50.7-9.7 5.1-.6 10.2.5 14.6 3l21.9 12.6c1.9 1.1 4 1.6 6 1.6zM158.6 149.1c1.2 2.1 3.1 3.5 5.3 4.1 2.2.6 4.5.3 6.6-.9 2.1-1.2 3.5-3.1 4.1-5.3.6-2.2.3-4.5-.9-6.6L154 106.3c-.6-1.1-1.9-1.5-3.1-1.4-1.6.1-3.8.9-6.4 2.4-2.6 1.5-4.4 3-5.3 4.3-.7 1-.9 2.3-.3 3.4l19.7 34.1zM140.6 173.5c2.1 1.2 4.5 1.4 6.6.9 2.2-.6 4.1-2 5.3-4.1 1.2-2.1 1.4-4.5.9-6.6-.5-2.1-2-4.1-4.1-5.3l-34.1-19.7c-1.1-.6-2.4-.4-3.4.3-1.3.9-2.8 2.7-4.3 5.3-1.5 2.6-2.3 4.8-2.4 6.4-.1 1.3.4 2.5 1.4 3.1l34.1 19.7zM203.8 137c0 2.4-1 4.6-2.5 6.2-1.6 1.6-3.7 2.5-6.2 2.5-2.4 0-4.6-1-6.2-2.5-1.6-1.6-2.5-3.7-2.5-6.2V97.7c0-1.3.8-2.3 2-2.8 1.4-.7 3.7-1.1 6.7-1.1s5.3.4 6.7 1.1c1.1.6 2 1.5 2 2.8V137zM231.6 149.1c-1.2 2.1-3.1 3.5-5.3 4.1-2.2.6-4.5.3-6.6-.9-2.1-1.2-3.5-3.1-4.1-5.3-.6-2.2-.3-4.5.9-6.6l19.7-34.1c.6-1.1 1.9-1.5 3.1-1.4 1.6.1 3.8.9 6.4 2.4 2.6 1.5 4.4 3 5.3 4.3.7 1 .9 2.3.3 3.4l-19.7 34.1zM249.6 173.5c-2.1 1.2-4.5 1.4-6.6.9-2.2-.6-4.1-2-5.3-4.1-1.2-2.1-1.4-4.5-.9-6.6.6-2.2 2-4.1 4.1-5.3l34.1-19.7c1.1-.6 2.4-.4 3.4.3 1.3.9 2.8 2.7 4.3 5.3 1.5 2.6 2.3 4.8 2.4 6.4.1 1.3-.4 2.5-1.4 3.1l-34.1 19.7z" class="st0" style="fill:#0770e3" transform="translate(-566.187 -169.038) scale(1.99578)"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
27
mobile/apps/auth/assets/custom-icons/icons/vhv.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) by Marsupilami -->
|
||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="1024" height="357" viewBox="-1.98252 -1.98252 201.02104 70.04904" id="svg3349">
|
||||
<defs id="defs3351"/>
|
||||
<path d="m 0,0 11.76,0 4.455,31.962 0.12,0 L 20.789,0 32.55,0 23.402,42.417 9.147,42.417 0,0" id="path3131" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 34.039,0 11.05,0 0,15.567 6.771,0 0,-15.567 11.05,0 0,42.417 -11.05,0 0,-17.465 -6.771,0 0,17.465 -11.05,0 0,-42.417" id="path3133" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 64.395,0 11.757,0 4.457,31.962 0.121,0 L 85.185,0 96.944,0 87.797,42.417 73.54,42.417 64.395,0" id="path3135" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 97.576,42.414 8.907,0 9.222,-42.41 -8.912,0 -9.217,42.41" id="path3137" style="fill:#f0ab00;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 111.026,42.424 8.908,-0.01 9.218,-42.41 -8.913,0 -9.213,42.42" id="path3139" style="fill:#f0ab00;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 124.564,42.414 8.906,0 9.217,-42.41 -8.906,0 -9.217,42.41" id="path3141" style="fill:#f0ab00;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 4.169,46.784 5.251,0 1.985,14.261 0.05,0 1.99,-14.261 5.247,0 -4.082,18.928 -6.363,0 -4.082,-18.928" id="path3143" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 19.356,46.784 11.216,0 0,4.031 -6.282,0 0,3.234 5.882,0 0,3.87 -5.882,0 0,3.763 6.517,0 0,4.03 -11.451,0 0,-18.928" id="path3145" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 54.93,52.247 0,-0.45 c 0,-1.117 -0.449,-2.043 -1.405,-2.043 -1.06,0 -1.538,0.823 -1.538,1.67 0,3.739 8.059,1.91 8.059,8.828 0,4.027 -2.357,5.832 -6.705,5.832 -4.082,0 -6.362,-1.404 -6.362,-5.331 l 0,-0.663 4.772,0 0,0.453 c 0,1.615 0.663,2.202 1.614,2.202 1.007,0 1.594,-0.798 1.594,-1.831 0,-3.739 -7.743,-1.88 -7.743,-8.586 0,-3.819 2.043,-5.913 6.205,-5.913 4.294,0 6.12,1.775 6.12,5.832 l -4.611,0" id="path3147" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 61.476,65.712 4.932,0 0,-18.928 -4.932,0 0,18.928 z" id="path3149" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 32.477,46.784 7.714,0 c 3.766,0 5.195,2.124 5.195,5.039 0,2.516 -0.979,4.16 -3.232,4.506 2.385,0.265 3.103,1.776 3.103,4.294 l 0,1.616 c 0,0.981 0,2.251 0.237,2.626 0.132,0.209 0.237,0.422 0.558,0.582 l 0,0.265 -5.25,0 C 40.325,64.705 40.325,62.9 40.325,62.109 l 0,-1.275 c 0,-2.147 -0.423,-2.704 -1.619,-2.704 l -1.296,0 0,7.582 -4.933,0 0,-18.928 z m 4.933,8.008 0.98,0 c 1.405,0 2.066,-0.9 2.066,-2.252 0,-1.541 -0.609,-2.202 -2.094,-2.202 l -0.952,0 0,4.454" id="path3151" style="fill:#1e1e1e;fill-opacity:1;fill-rule:evenodd;stroke:none"/>
|
||||
<path d="m 75.975,52.54 c 0,-2.147 -0.396,-2.786 -1.35,-2.786 -1.513,0 -1.67,1.381 -1.67,6.494 0,5.117 0.157,6.497 1.67,6.497 1.22,0 1.485,-1.063 1.485,-4.64 l 4.771,0 0,1.405 c 0,5.3 -3.101,6.574 -6.256,6.574 -5.54,0 -6.76,-2.784 -6.76,-9.836 0,-7.236 1.642,-9.833 6.76,-9.833 4.451,0 6.12,2.334 6.12,5.992 l 0,1.19 -4.77,0 0,-1.057" id="path3153" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 82.524,46.784 4.928,0 0,6.948 3.02,0 0,-6.948 4.932,0 0,18.928 -4.932,0 0,-7.793 -3.02,0 0,7.793 -4.928,0 0,-18.928" id="path3155" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 97.606,46.784 11.21,0 0,4.031 -6.282,0 0,3.234 5.886,0 0,3.87 -5.886,0 0,3.763 6.522,0 0,4.03 -11.45,0 0,-18.928" id="path3157" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 130.526,46.784 0,13.655 c 0,1.824 0.559,2.306 1.485,2.306 0.928,0 1.485,-0.482 1.485,-2.306 l 0,-13.655 4.929,0 0,12.408 c 0,5.298 -2.28,6.892 -6.414,6.892 -4.135,0 -6.412,-1.594 -6.412,-6.892 l 0,-12.408 4.927,0" id="path3159" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 140.415,46.784 5.51,0 3.185,11.665 0.05,0 0,-11.665 4.614,0 0,18.928 -5.407,0 -3.288,-11.689 -0.05,0 0,11.689 -4.613,0 0,-18.928" id="path3161" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 110.726,46.784 7.716,0 c 3.76,0 5.192,2.124 5.192,5.039 0,2.516 -0.979,4.16 -3.234,4.506 2.383,0.265 3.101,1.776 3.101,4.294 l 0,1.616 c 0,0.981 0,2.251 0.238,2.626 0.131,0.209 0.24,0.422 0.558,0.582 l 0,0.265 -5.251,0 c -0.477,-1.007 -0.477,-2.812 -0.477,-3.603 l 0,-1.275 c 0,-2.147 -0.422,-2.704 -1.612,-2.704 l -1.301,0 0,7.582 -4.93,0 0,-18.928 z m 4.93,8.008 0.979,0 c 1.404,0 2.07,-0.9 2.07,-2.252 0,-1.541 -0.61,-2.202 -2.095,-2.202 l -0.954,0 0,4.454" id="path3163" style="fill:#1e1e1e;fill-opacity:1;fill-rule:evenodd;stroke:none"/>
|
||||
<path d="m 162.334,55.428 6.361,0 0,10.284 -3.34,0 -0.113,-1.669 -0.05,0 c -0.662,1.617 -2.412,2.041 -4.082,2.041 -5.01,0 -5.459,-3.58 -5.459,-9.836 0,-6.336 1.22,-9.833 7.047,-9.833 3.502,0 5.993,1.775 5.993,6.36 l -4.771,0 c 0,-0.952 -0.07,-1.695 -0.266,-2.198 -0.185,-0.53 -0.554,-0.823 -1.139,-0.823 -1.612,0 -1.774,1.381 -1.774,6.494 0,5.117 0.161,6.497 1.67,6.497 1.034,0 1.639,-0.666 1.669,-3.978 l -1.75,0 0,-3.339" id="path3165" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 170.577,46.784 11.213,0 0,4.031 -6.28,0 0,3.234 5.885,0 0,3.87 -5.885,0 0,3.763 6.519,0 0,4.03 -11.452,0 0,-18.928" id="path3167" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 183.697,46.784 5.514,0 3.18,11.665 0.05,0 0,-11.665 4.615,0 0,18.928 -5.408,0 -3.284,-11.689 -0.05,0 0,11.689 -4.614,0 0,-18.928" id="path3169" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
</svg>
|
||||
<!-- version: 20110311, original size: 197.056 66.084, border: 3% -->
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
@@ -6,14 +6,14 @@
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"program": "auth/lib/main.dart",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"args": ["--dart-define", "endpoint=http://localhost:8080"]
|
||||
},
|
||||
{
|
||||
"name": "Auth Android Dev",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"args": [
|
||||
"--dart-define",
|
||||
"endpoint=http://192.168.1.3:8080",
|
||||
@@ -25,21 +25,21 @@
|
||||
"name": "Auth iOS Dev",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"args": ["--dart-define", "endpoint=http://192.168.1.30:8080"]
|
||||
},
|
||||
{
|
||||
"name": "Auth iOS Prod",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"args": ["--target", "lib/main.dart"]
|
||||
},
|
||||
{
|
||||
"name": "Auth Android Prod",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"args": ["--target", "lib/main.dart", "--flavor", "independent"]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,7 +3,6 @@ PODS:
|
||||
- Flutter
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- cupertino_http (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -40,6 +39,8 @@ PODS:
|
||||
- DKPhotoGallery/Resource (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- ente_qr (0.0.1):
|
||||
- Flutter
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
@@ -61,13 +62,12 @@ PODS:
|
||||
- Flutter
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- flutter_native_splash (0.0.1):
|
||||
- flutter_native_splash (2.4.3):
|
||||
- Flutter
|
||||
- flutter_secure_storage (6.0.0):
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- Toast
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -87,9 +87,9 @@ PODS:
|
||||
- qr_code_scanner (0.2.0):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- SDWebImage (5.21.0):
|
||||
- SDWebImage/Core (= 5.21.0)
|
||||
- SDWebImage/Core (5.21.0)
|
||||
- SDWebImage (5.21.1):
|
||||
- SDWebImage/Core (= 5.21.1)
|
||||
- SDWebImage/Core (5.21.1)
|
||||
- Sentry/HybridSDK (8.46.0)
|
||||
- sentry_flutter (8.14.2):
|
||||
- Flutter
|
||||
@@ -102,37 +102,43 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- sodium_libs (2.2.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.3):
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- "sqlite3 (3.46.1+1)":
|
||||
- "sqlite3/common (= 3.46.1+1)"
|
||||
- "sqlite3/common (3.46.1+1)"
|
||||
- "sqlite3/dbstatvtab (3.46.1+1)":
|
||||
- sqlite3 (3.50.2):
|
||||
- sqlite3/common (= 3.50.2)
|
||||
- sqlite3/common (3.50.2)
|
||||
- sqlite3/dbstatvtab (3.50.2):
|
||||
- sqlite3/common
|
||||
- "sqlite3/fts5 (3.46.1+1)":
|
||||
- sqlite3/fts5 (3.50.2):
|
||||
- sqlite3/common
|
||||
- "sqlite3/perf-threadsafe (3.46.1+1)":
|
||||
- sqlite3/math (3.50.2):
|
||||
- sqlite3/common
|
||||
- "sqlite3/rtree (3.46.1+1)":
|
||||
- sqlite3/perf-threadsafe (3.50.2):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.50.2):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- "sqlite3 (~> 3.46.0+1)"
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.50.1)
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3/fts5
|
||||
- sqlite3/math
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- SwiftyGif (5.4.5)
|
||||
- Toast (4.1.1)
|
||||
- ua_client_hints (1.4.1):
|
||||
- Flutter
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- ente_qr (from `.symlinks/plugins/ente_qr/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
||||
- fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`)
|
||||
@@ -155,8 +161,9 @@ DEPENDENCIES:
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
- ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
@@ -169,17 +176,18 @@ SPEC REPOS:
|
||||
- Sentry
|
||||
- sqlite3
|
||||
- SwiftyGif
|
||||
- Toast
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
cupertino_http:
|
||||
:path: ".symlinks/plugins/cupertino_http/darwin"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
ente_qr:
|
||||
:path: ".symlinks/plugins/ente_qr/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
file_saver:
|
||||
@@ -224,51 +232,54 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sodium_libs:
|
||||
:path: ".symlinks/plugins/sodium_libs/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/darwin"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
sqlite3_flutter_libs:
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
||||
ua_client_hints:
|
||||
:path: ".symlinks/plugins/ua_client_hints/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
|
||||
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
||||
app_links: f3e17e4ee5e357b39d8b95290a9b2c299fca71c6
|
||||
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
|
||||
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
ente_qr: f39434aa69ea0e71047b49316365b2737f8a20aa
|
||||
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40
|
||||
flutter_email_sender: e03bdda7637bcd3539bfe718fddd980e9508efaa
|
||||
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
|
||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||
flutter_local_notifications: df98d66e515e1ca797af436137b4459b160ad8c9
|
||||
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
|
||||
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
|
||||
local_auth_darwin: fa4b06454df7df8e97c18d7ee55151c57e7af0de
|
||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
objective_c: 77e887b5ba1827970907e10e832eec1683f3431d
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
|
||||
sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
|
||||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
|
||||
sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
sqlite3: 3e82a2daae39ba3b41ae6ee84a130494585460fc
|
||||
sqlite3_flutter_libs: 2c48c4ee7217fd653251975e43412250d5bcbbe2
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
|
||||
ua_client_hints: aeabd123262c087f0ce151ef96fa3ab77bfc8b38
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
|
||||
PODFILE CHECKSUM: 78f002751f1a8f65042b8da97902ba4124271c5a
|
||||
|
||||
@@ -2,20 +2,21 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/signed_out_event.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/locale.dart';
|
||||
import "package:ente_auth/onboarding/view/onboarding_page.dart";
|
||||
import 'package:ente_auth/services/authenticator_service.dart';
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/services/window_listener_service.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/settings/app_update_dialog.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_events/models/signed_in_event.dart';
|
||||
import 'package:ente_events/models/signed_out_event.dart';
|
||||
import 'package:ente_strings/l10n/strings_localizations.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
@@ -131,6 +132,7 @@ class _AppState extends State<App>
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
StringsLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
@@ -150,6 +152,7 @@ class _AppState extends State<App>
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
StringsLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
|
||||
@@ -1,95 +1,36 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' as io;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/endpoint_updated_event.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/signed_out_event.dart';
|
||||
import 'package:ente_auth/models/key_attributes.dart';
|
||||
import 'package:ente_auth/models/key_gen_result.dart';
|
||||
import 'package:ente_auth/models/private_key_attributes.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:ente_base/models/database.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class Configuration {
|
||||
class Configuration extends BaseConfiguration {
|
||||
Configuration._privateConstructor();
|
||||
|
||||
static final Configuration instance = Configuration._privateConstructor();
|
||||
static const endpoint = String.fromEnvironment(
|
||||
"endpoint",
|
||||
defaultValue: kDefaultProductionEndpoint,
|
||||
);
|
||||
static const emailKey = "email";
|
||||
static const keyAttributesKey = "key_attributes";
|
||||
|
||||
static const lastTempFolderClearTimeKey = "last_temp_folder_clear_time";
|
||||
static const keyKey = "key";
|
||||
static const secretKeyKey = "secret_key";
|
||||
static const authSecretKeyKey = "auth_secret_key";
|
||||
static const offlineAuthSecretKey = "offline_auth_secret_key";
|
||||
static const tokenKey = "token";
|
||||
static const encryptedTokenKey = "encrypted_token";
|
||||
static const userIDKey = "user_id";
|
||||
static const hasMigratedSecureStorageKey = "has_migrated_secure_storage";
|
||||
static const hasOptedForOfflineModeKey = "has_opted_for_offline_mode";
|
||||
static const endPointKey = "endpoint";
|
||||
final List<String> onlineSecureKeys = [
|
||||
keyKey,
|
||||
secretKeyKey,
|
||||
authSecretKeyKey,
|
||||
];
|
||||
|
||||
final kTempFolderDeletionTimeBuffer = const Duration(days: 1).inMicroseconds;
|
||||
|
||||
static final _logger = Logger("Configuration");
|
||||
|
||||
String? _cachedToken;
|
||||
late SharedPreferences _preferences;
|
||||
String? _key;
|
||||
String? _secretKey;
|
||||
String? _authSecretKey;
|
||||
String? _offlineAuthKey;
|
||||
late FlutterSecureStorage _secureStorage;
|
||||
late String _tempDirectory;
|
||||
|
||||
String? _volatilePassword;
|
||||
|
||||
Future<void> init() async {
|
||||
@override
|
||||
Future<void> init(List<EnteBaseDatabase> dbs) async {
|
||||
await super.init(dbs);
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
sqfliteFfiInit();
|
||||
_secureStorage = const FlutterSecureStorage(
|
||||
iOptions: IOSOptions(
|
||||
accessibility: KeychainAccessibility.first_unlock_this_device,
|
||||
),
|
||||
);
|
||||
_tempDirectory = (await DirectoryUtils.getDirectoryForInit()).path;
|
||||
final tempDirectory = io.Directory(_tempDirectory);
|
||||
try {
|
||||
final currentTime = DateTime.now().microsecondsSinceEpoch;
|
||||
if (tempDirectory.existsSync() &&
|
||||
(_preferences.getInt(lastTempFolderClearTimeKey) ?? 0) <
|
||||
(currentTime - kTempFolderDeletionTimeBuffer)) {
|
||||
await tempDirectory.delete(recursive: true);
|
||||
await _preferences.setInt(lastTempFolderClearTimeKey, currentTime);
|
||||
_logger.info("Cleared temp folder");
|
||||
} else {
|
||||
_logger.info("Skipping temp folder clear");
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.warning(e);
|
||||
}
|
||||
tempDirectory.createSync(recursive: true);
|
||||
await _initOnlineAccount();
|
||||
sqfliteFfiInit();
|
||||
await _initOfflineAccount();
|
||||
}
|
||||
|
||||
@@ -99,303 +40,10 @@ class Configuration {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _initOnlineAccount() async {
|
||||
if (!_preferences.containsKey(tokenKey)) {
|
||||
for (final key in onlineSecureKeys) {
|
||||
unawaited(
|
||||
_secureStorage.delete(
|
||||
key: key,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_key = await _secureStorage.read(
|
||||
key: keyKey,
|
||||
);
|
||||
_secretKey = await _secureStorage.read(
|
||||
key: secretKeyKey,
|
||||
);
|
||||
_authSecretKey = await _secureStorage.read(
|
||||
key: authSecretKeyKey,
|
||||
);
|
||||
if (_key == null) {
|
||||
await logout(autoLogout: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logout({bool autoLogout = false}) async {
|
||||
await _preferences.clear();
|
||||
for (String key in onlineSecureKeys) {
|
||||
await _secureStorage.delete(
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
await AuthenticatorDB.instance.clearTable();
|
||||
_key = null;
|
||||
_cachedToken = null;
|
||||
_secretKey = null;
|
||||
_authSecretKey = null;
|
||||
Bus.instance.fire(SignedOutEvent());
|
||||
}
|
||||
|
||||
Future<KeyGenResult> generateKey(String password) async {
|
||||
// Create a master key
|
||||
final masterKey = CryptoUtil.generateKey();
|
||||
|
||||
// Create a recovery key
|
||||
final recoveryKey = CryptoUtil.generateKey();
|
||||
|
||||
// Encrypt master key and recovery key with each other
|
||||
final encryptedMasterKey = CryptoUtil.encryptSync(masterKey, recoveryKey);
|
||||
final encryptedRecoveryKey = CryptoUtil.encryptSync(recoveryKey, masterKey);
|
||||
|
||||
// Derive a key from the password that will be used to encrypt and
|
||||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
|
||||
// Encrypt the key with this derived key
|
||||
final encryptedKeyData =
|
||||
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
|
||||
|
||||
// Generate a public-private keypair and encrypt the latter
|
||||
final keyPair = CryptoUtil.generateKeyPair();
|
||||
final encryptedSecretKeyData =
|
||||
CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
|
||||
|
||||
final attributes = KeyAttributes(
|
||||
CryptoUtil.bin2base64(kekSalt),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(keyPair.publicKey),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!),
|
||||
derivedKeyResult.memLimit,
|
||||
derivedKeyResult.opsLimit,
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
);
|
||||
final privateAttributes = PrivateKeyAttributes(
|
||||
CryptoUtil.bin2base64(masterKey),
|
||||
CryptoUtil.bin2hex(recoveryKey),
|
||||
CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
|
||||
);
|
||||
return KeyGenResult(attributes, privateAttributes, loginKey);
|
||||
}
|
||||
|
||||
Future<Tuple2<KeyAttributes, Uint8List>> getAttributesForNewPassword(
|
||||
String password,
|
||||
) async {
|
||||
// Get master key
|
||||
final masterKey = getKey();
|
||||
|
||||
// Derive a key from the password that will be used to encrypt and
|
||||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
|
||||
// Encrypt the key with this derived key
|
||||
final encryptedKeyData =
|
||||
CryptoUtil.encryptSync(masterKey!, derivedKeyResult.key);
|
||||
|
||||
final existingAttributes = getKeyAttributes();
|
||||
|
||||
final updatedAttributes = existingAttributes!.copyWith(
|
||||
kekSalt: CryptoUtil.bin2base64(kekSalt),
|
||||
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
memLimit: derivedKeyResult.memLimit,
|
||||
opsLimit: derivedKeyResult.opsLimit,
|
||||
);
|
||||
return Tuple2(updatedAttributes, loginKey);
|
||||
}
|
||||
|
||||
// decryptSecretsAndGetLoginKey decrypts the master key and recovery key
|
||||
// with the given password and save them in local secure storage.
|
||||
// This method also returns the keyEncKey that can be used for performing
|
||||
// SRP setup for existing users.
|
||||
Future<Uint8List> decryptSecretsAndGetKeyEncKey(
|
||||
String password,
|
||||
KeyAttributes attributes, {
|
||||
Uint8List? keyEncryptionKey,
|
||||
}) async {
|
||||
_logger.info('Start decryptAndSaveSecrets');
|
||||
keyEncryptionKey ??= await CryptoUtil.deriveKey(
|
||||
utf8.encode(password),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
|
||||
_logger.info('user-key done');
|
||||
Uint8List key;
|
||||
try {
|
||||
key = CryptoUtil.decryptSync(
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe('master-key failed, incorrect password?', e);
|
||||
throw Exception("Incorrect password");
|
||||
}
|
||||
_logger.info("master-key done");
|
||||
await setKey(CryptoUtil.bin2base64(key));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
key,
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
_logger.info("secret-key done");
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
_logger.info('appToken done');
|
||||
await setToken(
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
return keyEncryptionKey;
|
||||
}
|
||||
|
||||
Future<void> recover(String recoveryKey) async {
|
||||
// check if user has entered mnemonic code
|
||||
if (recoveryKey.contains(' ')) {
|
||||
final split = recoveryKey.split(' ');
|
||||
if (split.length != mnemonicKeyWordCount) {
|
||||
String wordThatIsFollowedByEmptySpaceInSplit = '';
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
String word = split[i];
|
||||
if (word.isEmpty) {
|
||||
wordThatIsFollowedByEmptySpaceInSplit =
|
||||
'\n\nExtra space after word at position $i';
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw AssertionError(
|
||||
'\nRecovery code should have $mnemonicKeyWordCount words, '
|
||||
'found ${split.length} words instead.$wordThatIsFollowedByEmptySpaceInSplit',
|
||||
);
|
||||
}
|
||||
recoveryKey = bip39.mnemonicToEntropy(recoveryKey);
|
||||
}
|
||||
final attributes = getKeyAttributes();
|
||||
Uint8List masterKey;
|
||||
try {
|
||||
masterKey = await CryptoUtil.decrypt(
|
||||
CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
CryptoUtil.hex2bin(recoveryKey),
|
||||
CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
rethrow;
|
||||
}
|
||||
await setKey(CryptoUtil.bin2base64(masterKey));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
masterKey,
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
await setToken(
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
}
|
||||
|
||||
String getHttpEndpoint() {
|
||||
return _preferences.getString(endPointKey) ?? endpoint;
|
||||
}
|
||||
|
||||
Future<void> setHttpEndpoint(String endpoint) async {
|
||||
await _preferences.setString(endPointKey, endpoint);
|
||||
Bus.instance.fire(EndpointUpdatedEvent());
|
||||
}
|
||||
|
||||
String? getToken() {
|
||||
_cachedToken ??= _preferences.getString(tokenKey);
|
||||
return _cachedToken;
|
||||
}
|
||||
|
||||
bool isLoggedIn() {
|
||||
return getToken() != null;
|
||||
}
|
||||
|
||||
Future<void> setToken(String token) async {
|
||||
_cachedToken = token;
|
||||
await _preferences.setString(tokenKey, token);
|
||||
Bus.instance.fire(SignedInEvent());
|
||||
}
|
||||
|
||||
Future<void> setEncryptedToken(String encryptedToken) async {
|
||||
await _preferences.setString(encryptedTokenKey, encryptedToken);
|
||||
}
|
||||
|
||||
String? getEncryptedToken() {
|
||||
return _preferences.getString(encryptedTokenKey);
|
||||
}
|
||||
|
||||
String? getEmail() {
|
||||
return _preferences.getString(emailKey);
|
||||
}
|
||||
|
||||
Future<void> setEmail(String email) async {
|
||||
await _preferences.setString(emailKey, email);
|
||||
}
|
||||
|
||||
int? getUserID() {
|
||||
return _preferences.getInt(userIDKey);
|
||||
}
|
||||
|
||||
Future<void> setUserID(int userID) async {
|
||||
await _preferences.setInt(userIDKey, userID);
|
||||
}
|
||||
|
||||
Future<void> setKeyAttributes(KeyAttributes attributes) async {
|
||||
await _preferences.setString(keyAttributesKey, attributes.toJson());
|
||||
}
|
||||
|
||||
KeyAttributes? getKeyAttributes() {
|
||||
final jsonValue = _preferences.getString(keyAttributesKey);
|
||||
if (jsonValue == null) {
|
||||
return null;
|
||||
} else {
|
||||
return KeyAttributes.fromJson(jsonValue);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setKey(String key) async {
|
||||
_key = key;
|
||||
await _secureStorage.write(
|
||||
key: keyKey,
|
||||
value: key,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setSecretKey(String? secretKey) async {
|
||||
_secretKey = secretKey;
|
||||
await _secureStorage.write(
|
||||
key: secretKeyKey,
|
||||
value: secretKey,
|
||||
);
|
||||
await super.logout();
|
||||
}
|
||||
|
||||
Future<void> setAuthSecretKey(String? authSecretKey) async {
|
||||
@@ -406,14 +54,6 @@ class Configuration {
|
||||
);
|
||||
}
|
||||
|
||||
Uint8List? getKey() {
|
||||
return _key == null ? null : CryptoUtil.base642bin(_key!);
|
||||
}
|
||||
|
||||
Uint8List? getSecretKey() {
|
||||
return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getAuthSecretKey() {
|
||||
return _authSecretKey == null
|
||||
? null
|
||||
@@ -426,24 +66,6 @@ class Configuration {
|
||||
: CryptoUtil.base642bin(_offlineAuthKey!);
|
||||
}
|
||||
|
||||
Uint8List getRecoveryKey() {
|
||||
final keyAttributes = getKeyAttributes()!;
|
||||
return CryptoUtil.decryptSync(
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
getKey()!,
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
);
|
||||
}
|
||||
|
||||
// Caution: This directory is cleared on app start
|
||||
String getTempDirectory() {
|
||||
return _tempDirectory;
|
||||
}
|
||||
|
||||
bool hasConfiguredAccount() {
|
||||
return getToken() != null && _key != null;
|
||||
}
|
||||
|
||||
bool hasOptedForOfflineMode() {
|
||||
return _preferences.getBool(hasOptedForOfflineModeKey) ?? false;
|
||||
}
|
||||
@@ -464,16 +86,4 @@ class Configuration {
|
||||
}
|
||||
await _preferences.setBool(hasOptedForOfflineModeKey, true);
|
||||
}
|
||||
|
||||
void setVolatilePassword(String volatilePassword) {
|
||||
_volatilePassword = volatilePassword;
|
||||
}
|
||||
|
||||
void resetVolatilePassword() {
|
||||
_volatilePassword = null;
|
||||
}
|
||||
|
||||
String? getVolatilePassword() {
|
||||
return _volatilePassword;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
import 'package:ente_events/models/event.dart';
|
||||
|
||||
class CodesUpdatedEvent extends Event {}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class EndpointUpdatedEvent extends Event {}
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
import 'package:ente_events/models/event.dart';
|
||||
|
||||
class IconsChangedEvent extends Event {}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
// NotificationEvent event is used to re-fresh the UI to show latest notification
|
||||
// (if any)
|
||||
class NotificationEvent extends Event {}
|
||||
@@ -1,3 +0,0 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class SignedInEvent extends Event {}
|
||||
@@ -1,3 +1 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class SignedOutEvent extends Event {}
|
||||
// TODO Implement this library.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
import 'package:ente_events/models/event.dart';
|
||||
|
||||
class TriggerLogoutEvent extends Event {}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_key.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
|
||||
class AuthenticatorGateway {
|
||||
late Dio _enteDio;
|
||||
|
||||
@@ -88,6 +88,8 @@
|
||||
"useRecoveryKey": "Χρήση κλειδιού ανάκτησης",
|
||||
"incorrectPasswordTitle": "Λάθος κωδικός πρόσβασης",
|
||||
"welcomeBack": "Καλωσορίσατε και πάλι!",
|
||||
"emailAlreadyRegistered": "Το email είναι ήδη καταχωρημένο.",
|
||||
"emailNotRegistered": "Το email δεν έχει καταχωρηθεί.",
|
||||
"madeWithLoveAtPrefix": "φτιαγμένη με ❤️ στο ",
|
||||
"supportDevs": "Εγγραφείτε στο <bold-green>ente</bold-green> για να μας υποστηρίξετε",
|
||||
"supportDiscount": "Χρησιμοποιήστε τον κωδικό κουπονιού \"AUTH\" για να λάβετε 10% έκπτωση για τον πρώτο χρόνο",
|
||||
@@ -171,6 +173,7 @@
|
||||
"invalidQRCode": "Μη έγκυρος κωδικός QR",
|
||||
"noRecoveryKeyTitle": "Χωρίς κλειδί ανάκτησης;",
|
||||
"enterEmailHint": "Εισάγετε τη διεύθυνση email σας",
|
||||
"enterNewEmailHint": "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας",
|
||||
"invalidEmailTitle": "Μη έγκυρη διεύθυνση email",
|
||||
"invalidEmailMessage": "Παρακαλούμε εισάγετε μια έγκυρη διεύθυνση email.",
|
||||
"deleteAccount": "Διαγραφή λογαριασμού",
|
||||
@@ -258,6 +261,10 @@
|
||||
"areYouSureYouWantToLogout": "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;",
|
||||
"yesLogout": "Ναι, αποσύνδεση",
|
||||
"exit": "Εξοδος",
|
||||
"theme": "Θέμα",
|
||||
"lightTheme": "Φωτεινό",
|
||||
"darkTheme": "Σκοτεινό",
|
||||
"systemTheme": "Σύστημα",
|
||||
"verifyingRecoveryKey": "Επαλήθευση κλειδιού ανάκτησης...",
|
||||
"recoveryKeyVerified": "Το κλειδί ανάκτησης επαληθεύτηκε",
|
||||
"recoveryKeySuccessBody": "Τέλεια! Το κλειδί ανάκτησης σας είναι έγκυρο. Σας ευχαριστούμε για την επαλήθευση.\n\nΠαρακαλώ θυμηθείτε να κρατήσετε το κλειδί ανάκτησης σας και σε αντίγραφο ασφαλείας.",
|
||||
@@ -490,5 +497,24 @@
|
||||
"appLockNotEnabled": "Το κλείδωμα εφαρμογής δεν είναι ενεργοποιημένο",
|
||||
"appLockNotEnabledDescription": "Παρακαλώ ενεργοποιήστε το κλείδωμα εφαρμογής μέσω της επιλογής Ασφάλεια > Κλείδωμα εφαρμογής",
|
||||
"authToViewPasskey": "Παρακαλώ πιστοποιηθείτε για να δείτε το κλειδί πρόσβασης",
|
||||
"appLockOfflineModeWarning": "Έχετε επιλέξει να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Αν ξεχάσετε τον κωδικό της εφαρμογής, θα κλειδωθείτε από την πρόσβαση στα δεδομένα σας."
|
||||
"appLockOfflineModeWarning": "Έχετε επιλέξει να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Αν ξεχάσετε τον κωδικό της εφαρμογής, θα κλειδωθείτε από την πρόσβαση στα δεδομένα σας.",
|
||||
"duplicateCodes": "Διπλότυποι κωδικοί",
|
||||
"noDuplicates": "✨ Δεν υπάρχουν διπλότυπα",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Δεν υπάρχουν διπλότυπα αρχεία που μπορούν να εκκαθαριστούν",
|
||||
"deduplicateCodes": "Διπλότυποι κωδικοί",
|
||||
"deselectAll": "Αποεπιλογή όλων",
|
||||
"selectAll": "Επιλογή όλων",
|
||||
"deleteDuplicates": "Διαγραφή διπλότυπων",
|
||||
"plainHTML": "Απλό HTML",
|
||||
"dropReviewiOS": "Αφήστε μια κριτική στο App Store",
|
||||
"dropReviewAndroid": "Αφήστε μια κριτική στο Play Store",
|
||||
"giveUsAStarOnGithub": "Δώστε μας ένα αστέρι στο Github",
|
||||
"free5GB": "5GB δωρεάν στο <bold-green>ente</bold-green> Photos",
|
||||
"freeStorageOffer": "10% έκπτωση στο <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Χρησιμοποιήστε τον κωδικό \"AUTH\" για να λάβετε 10% έκπτωση για τον πρώτο χρόνο",
|
||||
"advanced": "Για προχωρημένους",
|
||||
"algorithm": "Αλγόριθμος",
|
||||
"type": "Τύπος",
|
||||
"period": "Περίοδος",
|
||||
"digits": "Ψηφία"
|
||||
}
|
||||
@@ -111,6 +111,7 @@
|
||||
"importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
|
||||
"import2FasGuide": "Use the \"Settings->Backup -Export\" option in 2FAS.\n\nIf your backup is encrypted, you will need to enter the password to decrypt the backup",
|
||||
"importLastpassGuide": "Use the \"Transfer accounts\" option within Lastpass Authenticator Settings and press \"Export accounts to file\". Import the JSON downloaded.",
|
||||
"importProtonAuthGuide": "Use the \"Export\" option in Proton Authenticator Settings to export your codes.",
|
||||
"exportCodes": "Export codes",
|
||||
"importLabel": "Import",
|
||||
"importInstruction": "Please select a file that contains a list of your codes in the following format",
|
||||
@@ -519,5 +520,12 @@
|
||||
"algorithm": "Algorithm",
|
||||
"type": "Type",
|
||||
"period": "Period",
|
||||
"digits": "Digits"
|
||||
"digits": "Digits",
|
||||
"importFromGallery": "Import from gallery",
|
||||
"errorCouldNotReadImage": "Could not read the selected image file.",
|
||||
"errorInvalidQRCode": "Invalid QR Code",
|
||||
"errorInvalidQRCodeBody": "The scanned QR code is not a valid 2FA account.",
|
||||
"errorNoQRCode": "No QR code found",
|
||||
"errorGenericTitle": "An Error Occurred",
|
||||
"errorGenericBody": "An unexpected error occurred while importing."
|
||||
}
|
||||
@@ -45,7 +45,7 @@
|
||||
"timeBasedKeyType": "Oparte na czasie (TOTP)",
|
||||
"counterBasedKeyType": "Oparte na liczniku (HOTP)",
|
||||
"saveAction": "Zapisz",
|
||||
"nextTotpTitle": "następny",
|
||||
"nextTotpTitle": "dalej",
|
||||
"deleteCodeTitle": "Usunąć kod?",
|
||||
"deleteCodeMessage": "Czy na pewno chcesz usunąć ten kod? Ta akcja jest nieodwracalna.",
|
||||
"trashCode": "Przenieść kod do kosza?",
|
||||
|
||||
@@ -6,4 +6,4 @@ export "package:ente_auth/l10n/arb/app_localizations.dart"
|
||||
|
||||
extension AppLocalizationsX on BuildContext {
|
||||
AppLocalizations get l10n => AppLocalizations.of(this);
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,38 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import "package:ente_auth/app/view/app.dart";
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/logging/super_logging.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/locale.dart';
|
||||
import 'package:ente_auth/services/authenticator_service.dart';
|
||||
import 'package:ente_auth/services/billing_service.dart';
|
||||
import 'package:ente_auth/services/notification_service.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/services/window_listener_service.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/store/code_display_store.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
import 'package:ente_auth/ui/tools/lock_screen.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:ente_auth/utils/lock_screen_settings.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/window_protocol_handler.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:ente_lock_screen/lock_screen_settings.dart';
|
||||
import 'package:ente_lock_screen/ui/app_lock.dart';
|
||||
import 'package:ente_lock_screen/ui/lock_screen.dart';
|
||||
import 'package:ente_logging/logging.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
import 'package:ente_strings/l10n/strings_localizations.dart';
|
||||
import 'package:ente_ui/theme/theme_config.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
@@ -89,7 +93,9 @@ void main() async {
|
||||
}
|
||||
|
||||
Future<void> _runInForeground() async {
|
||||
AppThemeConfig.initialize(EnteApp.auth);
|
||||
final savedThemeMode = _themeMode(await AdaptiveTheme.getThemeMode());
|
||||
final configuration = Configuration.instance;
|
||||
return await _runWithLogs(() async {
|
||||
_logger.info("Starting app in foreground");
|
||||
try {
|
||||
@@ -103,12 +109,19 @@ Future<void> _runInForeground() async {
|
||||
runApp(
|
||||
AppLock(
|
||||
builder: (args) => App(locale: locale),
|
||||
lockScreen: const LockScreen(),
|
||||
lockScreen: LockScreen(configuration),
|
||||
enabled: await LockScreenSettings.instance.shouldShowLockScreen(),
|
||||
locale: locale,
|
||||
lightTheme: lightThemeData,
|
||||
darkTheme: darkThemeData,
|
||||
savedThemeMode: savedThemeMode,
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
localizationsDelegates: const [
|
||||
...StringsLocalizations.localizationsDelegates,
|
||||
...AppLocalizations.localizationsDelegates,
|
||||
],
|
||||
supportedLocales: appSupportedLocales,
|
||||
backgroundLockLatency: const Duration(seconds: 0),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -154,13 +167,13 @@ Future<void> _init(bool bool, {String? via}) async {
|
||||
await PreferenceService.instance.init();
|
||||
await CodeStore.instance.init();
|
||||
await CodeDisplayStore.instance.init();
|
||||
await Configuration.instance.init();
|
||||
await Network.instance.init();
|
||||
await UserService.instance.init();
|
||||
await Configuration.instance.init([AuthenticatorDB.instance]);
|
||||
await Network.instance.init(Configuration.instance);
|
||||
await UserService.instance.init(Configuration.instance, const HomePage());
|
||||
await AuthenticatorService.instance.init();
|
||||
await BillingService.instance.init();
|
||||
await NotificationService.instance.init();
|
||||
await UpdateService.instance.init();
|
||||
await IconUtils.instance.init();
|
||||
await LockScreenSettings.instance.init();
|
||||
await LockScreenSettings.instance.init(Configuration.instance);
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
const freeProductID = "free";
|
||||
const stripe = "stripe";
|
||||
const appStore = "appstore";
|
||||
const playStore = "playstore";
|
||||
|
||||
class Subscription {
|
||||
final String productID;
|
||||
final int storage;
|
||||
final String originalTransactionID;
|
||||
final String paymentProvider;
|
||||
final int expiryTime;
|
||||
final String price;
|
||||
final String period;
|
||||
final Attributes? attributes;
|
||||
|
||||
Subscription({
|
||||
required this.productID,
|
||||
required this.storage,
|
||||
required this.originalTransactionID,
|
||||
required this.paymentProvider,
|
||||
required this.expiryTime,
|
||||
required this.price,
|
||||
required this.period,
|
||||
this.attributes,
|
||||
});
|
||||
|
||||
bool isValid() {
|
||||
return expiryTime > DateTime.now().microsecondsSinceEpoch;
|
||||
}
|
||||
|
||||
bool isYearlyPlan() {
|
||||
return 'year' == period;
|
||||
}
|
||||
|
||||
static fromMap(Map<String, dynamic>? map) {
|
||||
if (map == null) return null;
|
||||
return Subscription(
|
||||
productID: map['productID'],
|
||||
storage: map['storage'],
|
||||
originalTransactionID: map['originalTransactionID'],
|
||||
paymentProvider: map['paymentProvider'],
|
||||
expiryTime: map['expiryTime'],
|
||||
price: map['price'],
|
||||
period: map['period'],
|
||||
attributes: map["attributes"] != null
|
||||
? Attributes.fromJson(map["attributes"])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Attributes {
|
||||
bool? isCancelled;
|
||||
String? customerID;
|
||||
|
||||
Attributes({
|
||||
this.isCancelled,
|
||||
this.customerID,
|
||||
});
|
||||
|
||||
Attributes.fromJson(dynamic json) {
|
||||
isCancelled = json["isCancelled"];
|
||||
customerID = json["customerID"];
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ente_auth/models/subscription.dart';
|
||||
|
||||
class UserDetails {
|
||||
final String email;
|
||||
final int usage;
|
||||
final int fileCount;
|
||||
final int sharedCollectionsCount;
|
||||
final Subscription subscription;
|
||||
final FamilyData? familyData;
|
||||
final ProfileData? profileData;
|
||||
|
||||
UserDetails(
|
||||
this.email,
|
||||
this.usage,
|
||||
this.fileCount,
|
||||
this.sharedCollectionsCount,
|
||||
this.subscription,
|
||||
this.familyData,
|
||||
this.profileData,
|
||||
);
|
||||
|
||||
bool isPartOfFamily() {
|
||||
return familyData?.members?.isNotEmpty ?? false;
|
||||
}
|
||||
|
||||
bool isFamilyAdmin() {
|
||||
assert(isPartOfFamily(), "verify user is part of family before calling");
|
||||
final FamilyMember currentUserMember = familyData!.members!
|
||||
.firstWhere((element) => element.email.trim() == email.trim());
|
||||
return currentUserMember.isAdmin;
|
||||
}
|
||||
|
||||
// getFamilyOrPersonalUsage will return total usage for family if user
|
||||
// belong to family group. Otherwise, it will return storage consumed by
|
||||
// current user
|
||||
int getFamilyOrPersonalUsage() {
|
||||
return isPartOfFamily() ? familyData!.getTotalUsage() : usage;
|
||||
}
|
||||
|
||||
int getFreeStorage() {
|
||||
return max(
|
||||
isPartOfFamily()
|
||||
? (familyData!.storage - familyData!.getTotalUsage())
|
||||
: (subscription.storage - (usage)),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
int getTotalStorage() {
|
||||
return isPartOfFamily() ? familyData!.storage : subscription.storage;
|
||||
}
|
||||
|
||||
factory UserDetails.fromMap(Map<String, dynamic> map) {
|
||||
return UserDetails(
|
||||
map['email'] as String,
|
||||
map['usage'] as int,
|
||||
(map['fileCount'] ?? 0) as int,
|
||||
(map['sharedCollectionsCount'] ?? 0) as int,
|
||||
Subscription.fromMap(map['subscription']),
|
||||
FamilyData.fromMap(map['familyData']),
|
||||
ProfileData.fromJson(map['profileData']),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FamilyMember {
|
||||
final String email;
|
||||
final int usage;
|
||||
final String id;
|
||||
final bool isAdmin;
|
||||
|
||||
FamilyMember(this.email, this.usage, this.id, this.isAdmin);
|
||||
|
||||
factory FamilyMember.fromMap(Map<String, dynamic> map) {
|
||||
return FamilyMember(
|
||||
(map['email'] ?? '') as String,
|
||||
map['usage'] as int,
|
||||
map['id'] as String,
|
||||
map['isAdmin'] as bool,
|
||||
);
|
||||
}
|
||||
}
|
||||
class ProfileData {
|
||||
bool canDisableEmailMFA;
|
||||
bool isEmailMFAEnabled;
|
||||
bool isTwoFactorEnabled;
|
||||
|
||||
// Constructor with default values
|
||||
ProfileData({
|
||||
this.canDisableEmailMFA = false,
|
||||
this.isEmailMFAEnabled = false,
|
||||
this.isTwoFactorEnabled = false,
|
||||
});
|
||||
|
||||
// Factory method to create ProfileData instance from JSON
|
||||
factory ProfileData.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) null;
|
||||
|
||||
return ProfileData(
|
||||
canDisableEmailMFA: json!['canDisableEmailMFA'] ?? false,
|
||||
isEmailMFAEnabled: json['isEmailMFAEnabled'] ?? false,
|
||||
isTwoFactorEnabled: json['isTwoFactorEnabled'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
// Method to convert ProfileData instance to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'canDisableEmailMFA': canDisableEmailMFA,
|
||||
'isEmailMFAEnabled': isEmailMFAEnabled,
|
||||
'isTwoFactorEnabled': isTwoFactorEnabled,
|
||||
};
|
||||
}
|
||||
String toJsonString() => json.encode(toJson());
|
||||
}
|
||||
class FamilyData {
|
||||
final List<FamilyMember>? members;
|
||||
|
||||
// Storage available based on the family plan
|
||||
final int storage;
|
||||
final int expiryTime;
|
||||
|
||||
FamilyData(this.members, this.storage, this.expiryTime);
|
||||
|
||||
int getTotalUsage() {
|
||||
return members!.map((e) => e.usage).toList().sum;
|
||||
}
|
||||
|
||||
static fromMap(Map<String, dynamic>? map) {
|
||||
if (map == null) return null;
|
||||
assert(map['members'] != null && map['members'].length >= 0);
|
||||
final members = List<FamilyMember>.from(
|
||||
map['members'].map((x) => FamilyMember.fromMap(x)),
|
||||
);
|
||||
return FamilyData(
|
||||
members,
|
||||
map['storage'] as int,
|
||||
map['expiryTime'] as int,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_accounts/pages/email_entry_page.dart';
|
||||
import 'package:ente_accounts/pages/login_page.dart';
|
||||
import 'package:ente_accounts/pages/password_entry_page.dart';
|
||||
import 'package:ente_accounts/pages/password_reentry_page.dart';
|
||||
import 'package:ente_auth/app/view/app.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/events/trigger_logout_event.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/locale.dart';
|
||||
import 'package:ente_auth/theme/text_style.dart';
|
||||
import 'package:ente_auth/ui/account/email_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/login_page.dart';
|
||||
import 'package:ente_auth/ui/account/logout_dialog.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/password_reentry_page.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
@@ -24,6 +23,7 @@ import 'package:ente_auth/ui/settings/language_picker.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
@@ -260,17 +260,22 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
void _navigateToSignUpPage() {
|
||||
Widget page;
|
||||
if (Configuration.instance.getEncryptedToken() == null) {
|
||||
page = const EmailEntryPage();
|
||||
page = EmailEntryPage(Configuration.instance);
|
||||
} else {
|
||||
// No key
|
||||
if (Configuration.instance.getKeyAttributes() == null) {
|
||||
// Never had a key
|
||||
page = const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.set,
|
||||
page = PasswordEntryPage(
|
||||
Configuration.instance,
|
||||
PasswordEntryMode.set,
|
||||
const HomePage(),
|
||||
);
|
||||
} else if (Configuration.instance.getKey() == null) {
|
||||
// Yet to decrypt the key
|
||||
page = const PasswordReentryPage();
|
||||
page = PasswordReentryPage(
|
||||
Configuration.instance,
|
||||
const HomePage(),
|
||||
);
|
||||
} else {
|
||||
// All is well, user just has not subscribed
|
||||
page = const HomePage();
|
||||
@@ -288,17 +293,22 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
void _navigateToSignInPage() {
|
||||
Widget page;
|
||||
if (Configuration.instance.getEncryptedToken() == null) {
|
||||
page = const LoginPage();
|
||||
page = LoginPage(Configuration.instance);
|
||||
} else {
|
||||
// No key
|
||||
if (Configuration.instance.getKeyAttributes() == null) {
|
||||
// Never had a key
|
||||
page = const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.set,
|
||||
page = PasswordEntryPage(
|
||||
Configuration.instance,
|
||||
PasswordEntryMode.set,
|
||||
const HomePage(),
|
||||
);
|
||||
} else if (Configuration.instance.getKey() == null) {
|
||||
// Yet to decrypt the key
|
||||
page = const PasswordReentryPage();
|
||||
page = PasswordReentryPage(
|
||||
Configuration.instance,
|
||||
const HomePage(),
|
||||
);
|
||||
} else {
|
||||
// All is well, user just has not subscribed
|
||||
// page = getSubscriptionPage(isOnBoarding: true);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/models/all_icon_data.dart';
|
||||
@@ -23,6 +22,7 @@ import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/trigger_logout_event.dart';
|
||||
import 'package:ente_auth/gateway/authenticator.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
@@ -17,6 +15,8 @@ import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/store/offline_authenticator_db.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_events/models/signed_in_event.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/models/billing_plan.dart';
|
||||
import 'package:ente_auth/models/subscription.dart';
|
||||
import 'package:ente_auth/models/billing_plan.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
const kWebPaymentRedirectUrl = "https://payments.ente.io/frameRedirect";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/icons_changed_event.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
enum CodeSortKey {
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/services/notification_service.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -4,13 +4,14 @@ import 'dart:io';
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:ente_base/ente_base.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class AuthenticatorDB {
|
||||
class AuthenticatorDB extends EnteBaseDatabase {
|
||||
static const _databaseName = "ente.authenticator.db";
|
||||
static const _databaseVersion = 1;
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import 'package:ente_auth/models/authenticator/entity_result.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/services/authenticator_service.dart';
|
||||
import 'package:ente_auth/store/offline_authenticator_db.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class CodeStore {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/onboarding/view/setup_enter_secret_key_page.dart';
|
||||
import 'package:ente_auth/onboarding/view/view_qr_page.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
@@ -22,6 +21,7 @@ import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:ente_lock_screen/local_authentication_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/dialog_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum DialogUserChoice { firstChoice, secondChoice }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/theme/text_style.dart';
|
||||
@@ -9,6 +8,7 @@ import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/components/models/custom_button_style.dart';
|
||||
import 'package:ente_auth/utils/debouncer.dart';
|
||||
import "package:ente_auth/utils/dialog_util.dart";
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/theme/effects.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
@@ -11,6 +10,7 @@ import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/components/separators.dart';
|
||||
import 'package:ente_auth/ui/components/text_input_widget.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
///Will return null if dismissed by tapping outside
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/components/menu_item_child_widgets.dart';
|
||||
import 'package:ente_auth/utils/debouncer.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/common/loading_widget.dart';
|
||||
import 'package:ente_auth/ui/components/separators.dart';
|
||||
import 'package:ente_auth/utils/debouncer.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/ui/common/loading_widget.dart';
|
||||
import 'package:ente_auth/utils/debouncer.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef OnChangedCallBack = void Function(bool);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CoachMarkWidget extends StatelessWidget {
|
||||
|
||||
@@ -3,8 +3,8 @@ import 'dart:io';
|
||||
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import 'package:ente_auth/events/icons_changed_event.dart';
|
||||
@@ -15,7 +15,6 @@ import 'package:ente_auth/onboarding/model/tag_enums.dart';
|
||||
import 'package:ente_auth/onboarding/view/common/tag_chip.dart';
|
||||
import 'package:ente_auth/onboarding/view/setup_enter_secret_key_page.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/store/code_display_store.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
@@ -34,11 +33,15 @@ import 'package:ente_auth/ui/reorder_codes_page.dart';
|
||||
import 'package:ente_auth/ui/scanner_page.dart';
|
||||
import 'package:ente_auth/ui/settings_page.dart';
|
||||
import 'package:ente_auth/ui/sort_option_menu.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/lock_screen_settings.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_lock_screen/lock_screen_settings.dart';
|
||||
import 'package:ente_lock_screen/ui/app_lock.dart';
|
||||
import 'package:ente_qr/ente_qr.dart';
|
||||
import 'package:ente_ui/pages/base_home_page.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -47,7 +50,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:move_to_background/move_to_background.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
class HomePage extends BaseHomePage {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
@@ -61,6 +64,7 @@ class _HomePageState extends State<HomePage> {
|
||||
);
|
||||
bool _hasLoaded = false;
|
||||
bool _isSettingsOpen = false;
|
||||
bool _isImportingFromGallery = false;
|
||||
final Logger _logger = Logger("HomePage");
|
||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
@@ -287,6 +291,63 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _importFromGalleryNative() async {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
if (_isImportingFromGallery) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isImportingFromGallery = true;
|
||||
|
||||
try {
|
||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
);
|
||||
|
||||
if (result == null || result.files.single.path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String imagePath = result.files.single.path!;
|
||||
final enteQr = EnteQr();
|
||||
final QrScanResult qrResult = await enteQr.scanQrFromImage(imagePath);
|
||||
|
||||
if (qrResult.success && qrResult.content != null) {
|
||||
try {
|
||||
final newCode = Code.fromOTPAuthUrl(qrResult.content!);
|
||||
await CodeStore.instance.addCode(newCode, shouldSync: false);
|
||||
// Focus the new code by searching
|
||||
if ((_allCodes?.where((e) => !e.hasError).length ?? 0) > 2) {
|
||||
_focusNewCode(newCode);
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.severe('Error adding code from QR scan', e);
|
||||
await showErrorDialog(
|
||||
context,
|
||||
l10n.errorInvalidQRCode,
|
||||
l10n.errorInvalidQRCodeBody,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_logger.warning('QR scan failed: ${qrResult.error}');
|
||||
await showErrorDialog(
|
||||
context,
|
||||
l10n.errorNoQRCode,
|
||||
qrResult.error ?? l10n.errorNoQRCode,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
l10n.errorGenericTitle,
|
||||
l10n.errorGenericBody,
|
||||
);
|
||||
} finally {
|
||||
_isImportingFromGallery = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _redirectToScannerPage() async {
|
||||
final Code? code = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
@@ -744,6 +805,14 @@ class _HomePageState extends State<HomePage> {
|
||||
labelWidget: SpeedDialLabelWidget(context.l10n.enterDetailsManually),
|
||||
onTap: _redirectToManualEntryPage,
|
||||
),
|
||||
if (PlatformUtil.isMobile())
|
||||
SpeedDialChild(
|
||||
child: const Icon(Icons.image),
|
||||
backgroundColor: Theme.of(context).colorScheme.fabBackgroundColor,
|
||||
foregroundColor: Theme.of(context).colorScheme.fabForegroundColor,
|
||||
labelWidget: SpeedDialLabelWidget(context.l10n.importFromGallery),
|
||||
onTap: _importFromGalleryNative,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
|
||||
class ScannerPage extends StatefulWidget {
|
||||
const ScannerPage({super.key});
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import 'package:ente_accounts/pages/change_email_dialog.dart';
|
||||
import 'package:ente_accounts/pages/delete_account_page.dart';
|
||||
import 'package:ente_accounts/pages/password_entry_page.dart';
|
||||
import 'package:ente_accounts/pages/recovery_key_page.dart';
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/account/change_email_dialog.dart';
|
||||
import 'package:ente_auth/ui/account/delete_account_page.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/recovery_key_page.dart';
|
||||
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
|
||||
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/components/menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/settings/common_settings.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:ente_lock_screen/local_authentication_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AccountSectionWidget extends StatelessWidget {
|
||||
@@ -81,8 +82,10 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.update,
|
||||
return PasswordEntryPage(
|
||||
Configuration.instance,
|
||||
PasswordEntryMode.update,
|
||||
const HomePage(),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -121,6 +124,7 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
Configuration.instance,
|
||||
recoveryKey,
|
||||
l10n.ok,
|
||||
showAppBar: true,
|
||||
@@ -151,8 +155,9 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final config = Configuration.instance;
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, const DeleteAccountPage());
|
||||
routeToPage(context, DeleteAccountPage(config));
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/services/deduplication_service.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/code_widget.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_lock_screen/local_authentication_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
@@ -2,8 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/export/ente.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/models/export/ente.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/dialog_widget.dart';
|
||||
@@ -14,6 +13,7 @@ import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/share_utils.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:ente_lock_screen/local_authentication_service.dart';
|
||||
import 'package:file_saver/file_saver.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/lastpass_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/proton_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/raivo_plain_text_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/two_fas_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import_page.dart';
|
||||
@@ -43,6 +44,9 @@ class ImportService {
|
||||
case ImportType.lastpass:
|
||||
await showLastpassImportInstruction(context);
|
||||
break;
|
||||
case ImportType.proton:
|
||||
await showProtonImportInstruction(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
171
mobile/apps/auth/lib/ui/settings/data/import/proton_import.dart
Normal file
@@ -0,0 +1,171 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/services/authenticator_service.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/common/progress_dialog.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/dialog_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
Future<void> showProtonImportInstruction(BuildContext context) async {
|
||||
final l10n = context.l10n;
|
||||
final result = await showDialogWidget(
|
||||
context: context,
|
||||
title: l10n.importFromApp("Proton Authenticator"),
|
||||
body: l10n.importProtonAuthGuide,
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: l10n.importSelectJsonFile,
|
||||
isInAlert: true,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.first,
|
||||
),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: context.l10n.cancel,
|
||||
buttonSize: ButtonSize.large,
|
||||
isInAlert: true,
|
||||
buttonAction: ButtonAction.second,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (result?.action != null && result!.action != ButtonAction.cancel) {
|
||||
if (result.action == ButtonAction.first) {
|
||||
await _pickProtonJsonFile(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pickProtonJsonFile(BuildContext context) async {
|
||||
final l10n = context.l10n;
|
||||
FilePickerResult? result = await FilePicker.platform
|
||||
.pickFiles(dialogTitle: l10n.importSelectJsonFile);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
final ProgressDialog progressDialog =
|
||||
createProgressDialog(context, l10n.pleaseWait);
|
||||
await progressDialog.show();
|
||||
try {
|
||||
String path = result.files.single.path!;
|
||||
int? count = await _processProtonExportFile(context, path, progressDialog);
|
||||
await progressDialog.hide();
|
||||
if (count != null) {
|
||||
await importSuccessDialog(context, count);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logger('ProtonImport')
|
||||
.severe('exception while processing proton import', e, s);
|
||||
await progressDialog.hide();
|
||||
await showErrorDialog(
|
||||
context,
|
||||
context.l10n.sorry,
|
||||
"${context.l10n.importFailureDescNew}\n Error: ${e.toString()}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int?> _processProtonExportFile(
|
||||
BuildContext context,
|
||||
String path,
|
||||
final ProgressDialog dialog,
|
||||
) async {
|
||||
File file = File(path);
|
||||
|
||||
final jsonString = await file.readAsString();
|
||||
final decodedJson = jsonDecode(jsonString);
|
||||
|
||||
// Validate that this is a Proton export
|
||||
if (decodedJson['version'] == null || decodedJson['entries'] == null) {
|
||||
await dialog.hide();
|
||||
await showErrorDialog(
|
||||
context,
|
||||
'Invalid Proton export',
|
||||
'The selected file is not a valid Proton Authenticator export.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final parsedCodes = <Code>[];
|
||||
final entries = decodedJson['entries'] as List;
|
||||
|
||||
for (var entry in entries) {
|
||||
try {
|
||||
final content = entry['content'];
|
||||
if (content == null) {
|
||||
continue; // Skip entries without content
|
||||
}
|
||||
|
||||
final entryType = content['entry_type'] as String?;
|
||||
if (entryType != 'Totp' && entryType != 'Steam') {
|
||||
// log warning
|
||||
Logger('ProtonImport').warning('Unsupported entry type: $entryType');
|
||||
continue; // Skip non-TOTP and non-Steam entries
|
||||
}
|
||||
|
||||
Code code;
|
||||
|
||||
if (entryType == 'Steam') {
|
||||
// Handle Steam entries with steam:// format
|
||||
final steamUri = content['uri'] as String?;
|
||||
if (steamUri == null || !steamUri.startsWith('steam://')) {
|
||||
continue; // Skip invalid Steam URIs
|
||||
}
|
||||
|
||||
final secret = steamUri.split('steam://')[1];
|
||||
final name = content['name'] as String? ?? '';
|
||||
|
||||
code = Code.fromAccountAndSecret(
|
||||
Type.steam,
|
||||
'', // Steam doesn't typically have separate account
|
||||
name, // Use name as issuer
|
||||
secret,
|
||||
null,
|
||||
Code.steamDigits,
|
||||
);
|
||||
} else {
|
||||
// Handle TOTP entries with otpauth:// format
|
||||
final otpUri = content['uri'] as String?;
|
||||
if (otpUri == null || !otpUri.startsWith('otpauth://')) {
|
||||
continue; // Skip invalid OTP URIs
|
||||
}
|
||||
// Create code from OTP auth URL
|
||||
code = Code.fromOTPAuthUrl(otpUri);
|
||||
}
|
||||
|
||||
// Add note if present
|
||||
final note = entry['note'] as String?;
|
||||
if (note != null && note.isNotEmpty) {
|
||||
code = code.copyWith(
|
||||
display: code.display.copyWith(note: note),
|
||||
);
|
||||
}
|
||||
|
||||
parsedCodes.add(code);
|
||||
} catch (e, s) {
|
||||
Logger('ProtonImport').warning('Failed to parse entry', e, s);
|
||||
// Continue processing other entries
|
||||
}
|
||||
}
|
||||
|
||||
// Add all parsed codes to the store
|
||||
for (final code in parsedCodes) {
|
||||
await CodeStore.instance.addCode(code, shouldSync: false);
|
||||
}
|
||||
|
||||
// Trigger sync
|
||||
unawaited(AuthenticatorService.instance.onlineSync());
|
||||
|
||||
return parsedCodes.length;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ enum ImportType {
|
||||
twoFas,
|
||||
bitwarden,
|
||||
lastpass,
|
||||
proton,
|
||||
}
|
||||
|
||||
class ImportCodePage extends StatelessWidget {
|
||||
@@ -29,6 +30,7 @@ class ImportCodePage extends StatelessWidget {
|
||||
ImportType.aegis,
|
||||
ImportType.bitwarden,
|
||||
ImportType.googleAuthenticator,
|
||||
ImportType.proton,
|
||||
ImportType.ravio,
|
||||
ImportType.lastpass,
|
||||
];
|
||||
@@ -51,6 +53,8 @@ class ImportCodePage extends StatelessWidget {
|
||||
return 'Bitwarden';
|
||||
case ImportType.lastpass:
|
||||
return 'LastPass Authenticator';
|
||||
case ImportType.proton:
|
||||
return 'Proton Authenticator';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/app/view/app.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/core/logging/super_logging.dart';
|
||||
import 'package:ente_auth/events/icons_changed_event.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/locale.dart';
|
||||
@@ -16,6 +14,8 @@ import 'package:ente_auth/ui/settings/common_settings.dart';
|
||||
import 'package:ente_auth/ui/settings/language_picker.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_logging/logging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AdvancedSectionWidget extends StatefulWidget {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_accounts/models/user_details.dart';
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/user_details.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/components/banner_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ente_accounts/models/user_details.dart';
|
||||
import 'package:ente_accounts/pages/request_pwd_verification_page.dart';
|
||||
import 'package:ente_accounts/pages/sessions_page.dart';
|
||||
import 'package:ente_accounts/services/passkey_service.dart';
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/user_details.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/services/passkey_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/account/request_pwd_verification_page.dart';
|
||||
import 'package:ente_auth/ui/account/sessions_page.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
|
||||
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
|
||||
@@ -17,14 +16,15 @@ import 'package:ente_auth/ui/components/menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/components/toggle_switch_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/common_settings.dart';
|
||||
import 'package:ente_auth/ui/settings/lock_screen/lock_screen_options.dart';
|
||||
import 'package:ente_auth/utils/auth_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/lock_screen_settings.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:ente_lock_screen/auth_util.dart';
|
||||
import 'package:ente_lock_screen/local_authentication_service.dart';
|
||||
import 'package:ente_lock_screen/lock_screen_settings.dart';
|
||||
import 'package:ente_lock_screen/ui/lock_screen_options.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
@@ -128,7 +128,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const SessionsPage();
|
||||
return SessionsPage(Configuration.instance);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -243,6 +243,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
await routeToPage(
|
||||
context,
|
||||
RequestPasswordVerificationPage(
|
||||
Configuration.instance,
|
||||
onPasswordVerified: (Uint8List keyEncryptionKey) async {
|
||||
final Uint8List loginKey =
|
||||
await CryptoUtil.deriveLoginKey(keyEncryptionKey);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/onboarding/view/onboarding_page.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/onboarding/view/onboarding_page.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
@@ -27,6 +26,7 @@ import 'package:ente_auth/ui/settings/title_bar_widget.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_lock_screen/local_authentication_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:confetti/confetti.dart';
|
||||
import "package:dio/dio.dart";
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/ui/common/loading_widget.dart';
|
||||
import 'package:ente_auth/ui/common/progress_dialog.dart';
|
||||
@@ -16,6 +15,7 @@ import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:io';
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:email_validator/email_validator.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/logging/super_logging.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/dialog_widget.dart';
|
||||
@@ -14,11 +13,11 @@ import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/share_utils.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_logging/logging.dart';
|
||||
import "package:file_saver/file_saver.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import "package:intl/intl.dart";
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
@@ -59,14 +59,14 @@ class PlatformUtil {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
await FileSaver.instance.saveAs(
|
||||
name: fileName,
|
||||
ext: extension,
|
||||
fileExtension: extension,
|
||||
bytes: bytes,
|
||||
mimeType: type,
|
||||
);
|
||||
} else {
|
||||
await FileSaver.instance.saveFile(
|
||||
name: fileName,
|
||||
ext: extension,
|
||||
fileExtension: extension,
|
||||
bytes: bytes,
|
||||
mimeType: type,
|
||||
);
|
||||
|
||||
33
mobile/apps/auth/plugins/qr/.metadata
Normal file
@@ -0,0 +1,33 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
|
||||
channel: "stable"
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: android
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: ios
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
8
mobile/apps/auth/plugins/qr/CHANGELOG.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## 0.0.1
|
||||
|
||||
* Initial release of ente_qr plugin
|
||||
* Support for scanning QR codes from image files
|
||||
* Android implementation using ZXing library
|
||||
* iOS implementation using Core Image framework
|
||||
* Comprehensive error handling
|
||||
* Example app demonstrating usage with file picker
|
||||
170
mobile/apps/auth/plugins/qr/README.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# ente_qr
|
||||
|
||||
A Flutter plugin for scanning QR codes from image files. This plugin provides a simple interface to scan QR codes from images on both Android and iOS platforms.
|
||||
|
||||
## Features
|
||||
|
||||
- Scan QR codes from image files
|
||||
- Support for Android (using ZXing library)
|
||||
- Support for iOS (using AVFoundation/Core Image)
|
||||
- Returns structured results with error handling
|
||||
- Works with images from file picker or camera
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Platform | Support |
|
||||
|----------|---------|
|
||||
| Android | ✅ |
|
||||
| iOS | ✅ |
|
||||
| Web | ❌ |
|
||||
| macOS | ❌ |
|
||||
| Windows | ❌ |
|
||||
| Linux | ❌ |
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your package's `pubspec.yaml` file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
ente_qr:
|
||||
path: path/to/ente_qr
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```dart
|
||||
import 'package:ente_qr/ente_qr.dart';
|
||||
|
||||
final qr = EnteQr();
|
||||
|
||||
// Scan QR code from an image file
|
||||
final result = await qr.scanQrFromImage('/path/to/image.jpg');
|
||||
|
||||
if (result.success) {
|
||||
print('QR Code content: ${result.content}');
|
||||
} else {
|
||||
print('Error: ${result.error}');
|
||||
}
|
||||
```
|
||||
|
||||
### Complete Example with File Picker
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:ente_qr/ente_qr.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
|
||||
class QrScannerPage extends StatefulWidget {
|
||||
@override
|
||||
_QrScannerPageState createState() => _QrScannerPageState();
|
||||
}
|
||||
|
||||
class _QrScannerPageState extends State<QrScannerPage> {
|
||||
final _enteQr = EnteQr();
|
||||
String _result = 'No QR code scanned';
|
||||
|
||||
Future<void> _scanQrFromImage() async {
|
||||
// Pick an image file
|
||||
FilePickerResult? fileResult = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
allowMultiple: false,
|
||||
);
|
||||
|
||||
if (fileResult != null && fileResult.files.single.path != null) {
|
||||
final imagePath = fileResult.files.single.path!;
|
||||
|
||||
// Scan QR code from the selected image
|
||||
final qrResult = await _enteQr.scanQrFromImage(imagePath);
|
||||
|
||||
setState(() {
|
||||
if (qrResult.success) {
|
||||
_result = 'QR Code: ${qrResult.content}';
|
||||
} else {
|
||||
_result = 'Error: ${qrResult.error}';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('QR Scanner')),
|
||||
body: Column(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: _scanQrFromImage,
|
||||
child: Text('Pick Image and Scan QR'),
|
||||
),
|
||||
Text(_result),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### EnteQr
|
||||
|
||||
The main class for QR code scanning operations.
|
||||
|
||||
#### Methods
|
||||
|
||||
##### `scanQrFromImage(String imagePath)`
|
||||
|
||||
Scans a QR code from an image file.
|
||||
|
||||
**Parameters:**
|
||||
- `imagePath` (String): The file path to the image containing the QR code
|
||||
|
||||
**Returns:**
|
||||
- `Future<QrScanResult>`: A result object containing either the QR code content or an error
|
||||
|
||||
### QrScanResult
|
||||
|
||||
The result object returned by QR scanning operations.
|
||||
|
||||
**Properties:**
|
||||
- `content` (String?): The QR code content if successful, null otherwise
|
||||
- `error` (String?): Error message if scanning failed, null otherwise
|
||||
- `success` (bool): Whether the scanning operation was successful
|
||||
|
||||
**Factory Constructors:**
|
||||
- `QrScanResult.success(String content)`: Creates a successful result
|
||||
- `QrScanResult.error(String error)`: Creates an error result
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Android
|
||||
|
||||
The Android implementation uses the ZXing library (com.google.zxing) for QR code detection:
|
||||
- `com.journeyapps:zxing-android-embedded:4.3.0`
|
||||
- `com.google.zxing:core:3.5.1`
|
||||
|
||||
### iOS
|
||||
|
||||
The iOS implementation uses Core Image framework's built-in QR code detection capabilities via `CIDetector`.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The plugin provides comprehensive error handling for common scenarios:
|
||||
- File not found
|
||||
- Invalid image format
|
||||
- No QR code found in image
|
||||
- Platform-specific errors
|
||||
- Unexpected errors
|
||||
|
||||
All errors are returned as part of the `QrScanResult` object with descriptive error messages.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the same license as the Ente project.
|
||||
1
mobile/apps/auth/plugins/qr/analysis_options.yaml
Normal file
@@ -0,0 +1 @@
|
||||
include: ../../analysis_options.yaml
|
||||
65
mobile/apps/auth/plugins/qr/android/build.gradle
Normal file
@@ -0,0 +1,65 @@
|
||||
group 'io.ente.auth.ente_qr'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
test.java.srcDirs += 'src/test/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
||||
implementation 'com.google.zxing:core:3.5.1'
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
mobile/apps/auth/plugins/qr/android/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'ente_qr'
|
||||
@@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.ente.auth.ente_qr">
|
||||
</manifest>
|
||||
@@ -0,0 +1,138 @@
|
||||
package io.ente.auth.ente_qr
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.annotation.NonNull
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.BinaryBitmap
|
||||
import com.google.zxing.DecodeHintType
|
||||
import com.google.zxing.MultiFormatReader
|
||||
import com.google.zxing.NotFoundException
|
||||
import com.google.zxing.RGBLuminanceSource
|
||||
import com.google.zxing.Result as ZXingResult
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
/** EnteQrPlugin */
|
||||
class EnteQrPlugin: FlutterPlugin, MethodCallHandler {
|
||||
/// The MethodChannel that will the communication between Flutter and native Android
|
||||
///
|
||||
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||
/// when the Flutter Engine is detached from the Activity
|
||||
private lateinit var channel : MethodChannel
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "ente_qr")
|
||||
channel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||
when (call.method) {
|
||||
"getPlatformVersion" -> {
|
||||
result.success("Android ${android.os.Build.VERSION.RELEASE}")
|
||||
}
|
||||
"scanQrFromImage" -> {
|
||||
val imagePath = call.argument<String>("imagePath")
|
||||
if (imagePath == null) {
|
||||
result.success(mapOf(
|
||||
"success" to false,
|
||||
"error" to "Image path is required"
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val qrResult = scanQrCode(imagePath)
|
||||
result.success(qrResult)
|
||||
} catch (e: Exception) {
|
||||
result.success(mapOf(
|
||||
"success" to false,
|
||||
"error" to "Error scanning QR code: ${e.message}"
|
||||
))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanQrCode(imagePath: String): Map<String, Any> {
|
||||
try {
|
||||
val file = File(imagePath)
|
||||
if (!file.exists()) {
|
||||
return mapOf(
|
||||
"success" to false,
|
||||
"error" to "Image file not found: $imagePath"
|
||||
)
|
||||
}
|
||||
|
||||
var bitmap = BitmapFactory.decodeFile(imagePath)
|
||||
if (bitmap == null) {
|
||||
return mapOf(
|
||||
"success" to false,
|
||||
"error" to "Unable to decode image file"
|
||||
)
|
||||
}
|
||||
|
||||
// Try multiple times with different image sizes like Aegis does
|
||||
for (i in 0..2) {
|
||||
if (i != 0) {
|
||||
// Resize bitmap for subsequent attempts
|
||||
val newWidth = bitmap.width / (i * 2)
|
||||
val newHeight = bitmap.height / (i * 2)
|
||||
if (newWidth > 0 && newHeight > 0) {
|
||||
bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
val pixels = IntArray(width * height)
|
||||
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
val source = RGBLuminanceSource(width, height, pixels)
|
||||
val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
|
||||
|
||||
val reader = MultiFormatReader()
|
||||
val hints = HashMap<DecodeHintType, Any>()
|
||||
hints[DecodeHintType.POSSIBLE_FORMATS] = listOf(BarcodeFormat.QR_CODE)
|
||||
hints[DecodeHintType.TRY_HARDER] = true
|
||||
hints[DecodeHintType.ALSO_INVERTED] = true
|
||||
reader.setHints(hints)
|
||||
|
||||
val qrResult: ZXingResult = reader.decode(binaryBitmap)
|
||||
|
||||
return mapOf(
|
||||
"success" to true,
|
||||
"content" to qrResult.text
|
||||
)
|
||||
} catch (e: NotFoundException) {
|
||||
// Continue to next iteration
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
"success" to false,
|
||||
"error" to "No QR code found in image"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
return mapOf(
|
||||
"success" to false,
|
||||
"error" to "Error scanning QR code: ${e.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel.setMethodCallHandler(null)
|
||||
}
|
||||
}
|
||||
77
mobile/apps/auth/plugins/qr/ios/Classes/EnteQrPlugin.swift
Normal file
@@ -0,0 +1,77 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
public class EnteQrPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "ente_qr", binaryMessenger: registrar.messenger())
|
||||
let instance = EnteQrPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getPlatformVersion":
|
||||
result("iOS " + UIDevice.current.systemVersion)
|
||||
case "scanQrFromImage":
|
||||
guard let args = call.arguments as? [String: Any],
|
||||
let imagePath = args["imagePath"] as? String else {
|
||||
result([
|
||||
"success": false,
|
||||
"error": "Image path is required"
|
||||
])
|
||||
return
|
||||
}
|
||||
|
||||
scanQrCode(from: imagePath, result: result)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
private func scanQrCode(from imagePath: String, result: @escaping FlutterResult) {
|
||||
guard let image = UIImage(contentsOfFile: imagePath) else {
|
||||
result([
|
||||
"success": false,
|
||||
"error": "Unable to load image from path: \(imagePath)"
|
||||
])
|
||||
return
|
||||
}
|
||||
|
||||
guard let cgImage = image.cgImage else {
|
||||
result([
|
||||
"success": false,
|
||||
"error": "Unable to get CGImage from UIImage"
|
||||
])
|
||||
return
|
||||
}
|
||||
|
||||
let detector = CIDetector(ofType: CIDetectorTypeQRCode,
|
||||
context: nil,
|
||||
options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
|
||||
|
||||
guard let qrDetector = detector else {
|
||||
result([
|
||||
"success": false,
|
||||
"error": "Unable to create QR code detector"
|
||||
])
|
||||
return
|
||||
}
|
||||
|
||||
let ciImage = CIImage(cgImage: cgImage)
|
||||
let features = qrDetector.features(in: ciImage)
|
||||
|
||||
if let qrFeature = features.first as? CIQRCodeFeature,
|
||||
let messageString = qrFeature.messageString {
|
||||
result([
|
||||
"success": true,
|
||||
"content": messageString
|
||||
])
|
||||
} else {
|
||||
result([
|
||||
"success": false,
|
||||
"error": "No QR code found in image"
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
23
mobile/apps/auth/plugins/qr/ios/ente_qr.podspec
Normal file
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint ente_qr.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ente_qr'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'A QR code reader plugin for Ente.'
|
||||
s.description = <<-DESC
|
||||
A QR code reader plugin for Ente.
|
||||
DESC
|
||||
s.homepage = 'https://ente.io'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Ente' => 'team@ente.io' }
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'Flutter'
|
||||
s.platform = :ios, '9.0'
|
||||
|
||||
# Flutter.framework does not contain a i386 slice.
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
|
||||
s.swift_version = '5.0'
|
||||
end
|
||||
30
mobile/apps/auth/plugins/qr/lib/ente_qr.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:ente_qr/ente_qr_platform_interface.dart';
|
||||
|
||||
export 'ente_qr_platform_interface.dart' show QrScanResult;
|
||||
|
||||
class EnteQr {
|
||||
Future<String?> getPlatformVersion() {
|
||||
return EnteQrPlatform.instance.getPlatformVersion();
|
||||
}
|
||||
|
||||
/// Scans a QR code from an image file at the given path.
|
||||
///
|
||||
/// [imagePath] - The file path to the image containing the QR code
|
||||
///
|
||||
/// Returns a [QrScanResult] containing either the QR code content on success
|
||||
/// or an error message on failure.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final qr = EnteQr();
|
||||
/// final result = await qr.scanQrFromImage('/path/to/image.jpg');
|
||||
/// if (result.success) {
|
||||
/// print('QR Code content: ${result.content}');
|
||||
/// } else {
|
||||
/// print('Error: ${result.error}');
|
||||
/// }
|
||||
/// ```
|
||||
Future<QrScanResult> scanQrFromImage(String imagePath) {
|
||||
return EnteQrPlatform.instance.scanQrFromImage(imagePath);
|
||||
}
|
||||
}
|
||||
52
mobile/apps/auth/plugins/qr/lib/ente_qr_method_channel.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:ente_qr/ente_qr_platform_interface.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// An implementation of [EnteQrPlatform] that uses method channels.
|
||||
class MethodChannelEnteQr extends EnteQrPlatform {
|
||||
/// The method channel used to interact with the native platform.
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('ente_qr');
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version =
|
||||
await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||
return version;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<QrScanResult> scanQrFromImage(String imagePath) async {
|
||||
try {
|
||||
final dynamic result = await methodChannel.invokeMethod(
|
||||
'scanQrFromImage',
|
||||
{'imagePath': imagePath},
|
||||
);
|
||||
|
||||
if (result == null) {
|
||||
return QrScanResult.error('Failed to scan QR code');
|
||||
}
|
||||
|
||||
// Convert to Map<String, dynamic> safely
|
||||
final Map<String, dynamic> resultMap =
|
||||
Map<String, dynamic>.from(result as Map);
|
||||
|
||||
final bool success = resultMap['success'] as bool? ?? false;
|
||||
if (success) {
|
||||
final String? content = resultMap['content'] as String?;
|
||||
if (content != null && content.isNotEmpty) {
|
||||
return QrScanResult.success(content);
|
||||
} else {
|
||||
return QrScanResult.error('No QR code found in image');
|
||||
}
|
||||
} else {
|
||||
final String? error = resultMap['error'] as String?;
|
||||
return QrScanResult.error(error ?? 'Unknown error occurred');
|
||||
}
|
||||
} on PlatformException catch (e) {
|
||||
return QrScanResult.error('Platform error: ${e.message}');
|
||||
} catch (e) {
|
||||
return QrScanResult.error('Unexpected error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:ente_qr/ente_qr_method_channel.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
/// The result of QR code scanning
|
||||
class QrScanResult {
|
||||
final String? content;
|
||||
final String? error;
|
||||
final bool success;
|
||||
|
||||
const QrScanResult({
|
||||
this.content,
|
||||
this.error,
|
||||
required this.success,
|
||||
});
|
||||
|
||||
factory QrScanResult.success(String content) {
|
||||
return QrScanResult(
|
||||
content: content,
|
||||
success: true,
|
||||
);
|
||||
}
|
||||
|
||||
factory QrScanResult.error(String error) {
|
||||
return QrScanResult(
|
||||
error: error,
|
||||
success: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class EnteQrPlatform extends PlatformInterface {
|
||||
/// Constructs a EnteQrPlatform.
|
||||
EnteQrPlatform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static EnteQrPlatform _instance = MethodChannelEnteQr();
|
||||
|
||||
/// The default instance of [EnteQrPlatform] to use.
|
||||
///
|
||||
/// Defaults to [MethodChannelEnteQr].
|
||||
static EnteQrPlatform get instance => _instance;
|
||||
|
||||
/// Platform-specific implementations should set this with their own
|
||||
/// platform-specific class that extends [EnteQrPlatform] when
|
||||
/// they register themselves.
|
||||
static set instance(EnteQrPlatform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<String?> getPlatformVersion() {
|
||||
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||
}
|
||||
|
||||
/// Scans a QR code from an image file at the given path.
|
||||
/// Returns the QR code content as a string if successful, null otherwise.
|
||||
Future<QrScanResult> scanQrFromImage(String imagePath) {
|
||||
throw UnimplementedError('scanQrFromImage() has not been implemented.');
|
||||
}
|
||||
}
|
||||
213
mobile/apps/auth/plugins/qr/pubspec.lock
Normal file
@@ -0,0 +1,213 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
plugin_platform_interface:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
29
mobile/apps/auth/plugins/qr/pubspec.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: ente_qr
|
||||
description: "A QR code reader plugin for Ente."
|
||||
version: 0.0.1
|
||||
homepage: https://ente.io
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.3 <4.0.0'
|
||||
flutter: '>=3.3.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
plugin_platform_interface: ^2.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^3.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: io.ente.auth.ente_qr
|
||||
pluginClass: EnteQrPlugin
|
||||
ios:
|
||||
pluginClass: EnteQrPlugin
|
||||
32
mobile/apps/auth/plugins/qr/test/ente_qr_test.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:ente_qr/ente_qr.dart';
|
||||
import 'package:ente_qr/ente_qr_method_channel.dart';
|
||||
import 'package:ente_qr/ente_qr_platform_interface.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
class MockEnteQrPlatform
|
||||
with MockPlatformInterfaceMixin
|
||||
implements EnteQrPlatform {
|
||||
@override
|
||||
Future<String?> getPlatformVersion() => Future.value('42');
|
||||
|
||||
@override
|
||||
Future<QrScanResult> scanQrFromImage(String imagePath) =>
|
||||
Future.value(QrScanResult.error('Mock implementation'));
|
||||
}
|
||||
|
||||
void main() {
|
||||
final EnteQrPlatform initialPlatform = EnteQrPlatform.instance;
|
||||
|
||||
test('$MethodChannelEnteQr is the default instance', () {
|
||||
expect(initialPlatform, isInstanceOf<MethodChannelEnteQr>());
|
||||
});
|
||||
|
||||
test('getPlatformVersion', () async {
|
||||
final EnteQr enteQrPlugin = EnteQr();
|
||||
final MockEnteQrPlatform fakePlatform = MockEnteQrPlatform();
|
||||
EnteQrPlatform.instance = fakePlatform;
|
||||
|
||||
expect(await enteQrPlugin.getPlatformVersion(), '42');
|
||||
});
|
||||
}
|
||||
@@ -393,6 +393,27 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
ente_accounts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../packages/accounts"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_base:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../packages/base"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_configuration:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../packages/configuration"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_crypto_dart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -402,6 +423,62 @@ packages:
|
||||
url: "https://github.com/ente-io/ente_crypto_dart.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
ente_events:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../packages/events"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_lock_screen:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../packages/lock_screen"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_logging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../packages/logging"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_network:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../packages/network"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_qr:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "plugins/qr"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
ente_strings:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../packages/strings"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_ui:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../packages/ui"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_utils:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../../packages/utils"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
event_bus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -462,18 +539,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: ef9908739bdd9c476353d6adff72e88fd00c625f5b959ae23f7567bd5137db0a
|
||||
sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
version: "10.3.2"
|
||||
file_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_saver
|
||||
sha256: "448b1e30142cffe52f37ee085ea9ca50670d5425bb09b649d193549b2dcf6e26"
|
||||
sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
version: "0.3.1"
|
||||
fixnum:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -485,10 +562,11 @@ packages:
|
||||
fk_user_agent:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fk_user_agent
|
||||
sha256: fd6c94e120786985a292d12f61422a581f4e851148d5940af38b819357b8ad0d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "."
|
||||
ref: "458046cd9a88924e5074d96ba45397219d53b230"
|
||||
resolved-ref: "458046cd9a88924e5074d96ba45397219d53b230"
|
||||
url: "https://github.com/flutter-fast-kit/fk_user_agent"
|
||||
source: git
|
||||
version: "2.1.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
@@ -538,21 +616,19 @@ packages:
|
||||
flutter_inappwebview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: flutter_inappwebview
|
||||
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
|
||||
source: git
|
||||
version: "6.2.0-beta.3"
|
||||
name: flutter_inappwebview
|
||||
sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.5"
|
||||
flutter_inappwebview_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: flutter_inappwebview_android
|
||||
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
|
||||
source: git
|
||||
version: "1.2.0-beta.3"
|
||||
name: flutter_inappwebview_android
|
||||
sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
flutter_inappwebview_internal_annotations:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -564,48 +640,43 @@ packages:
|
||||
flutter_inappwebview_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: flutter_inappwebview_ios
|
||||
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
|
||||
source: git
|
||||
version: "1.2.0-beta.3"
|
||||
name: flutter_inappwebview_ios
|
||||
sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
flutter_inappwebview_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: flutter_inappwebview_macos
|
||||
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
|
||||
source: git
|
||||
version: "1.2.0-beta.3"
|
||||
name: flutter_inappwebview_macos
|
||||
sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
flutter_inappwebview_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: flutter_inappwebview_platform_interface
|
||||
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
|
||||
source: git
|
||||
version: "1.4.0-beta.3"
|
||||
name: flutter_inappwebview_platform_interface
|
||||
sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0+1"
|
||||
flutter_inappwebview_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: flutter_inappwebview_web
|
||||
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
|
||||
source: git
|
||||
version: "1.2.0-beta.3"
|
||||
name: flutter_inappwebview_web
|
||||
sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
flutter_inappwebview_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: flutter_inappwebview_windows
|
||||
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
|
||||
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
|
||||
source: git
|
||||
version: "0.7.0-beta.3"
|
||||
name: flutter_inappwebview_windows
|
||||
sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -887,7 +958,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
@@ -1081,10 +1152,11 @@ packages:
|
||||
move_to_background:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: move_to_background
|
||||
sha256: "00caad17a6ce149910777131503f43f8ed80025681f94684e3a6a87d979b914c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "."
|
||||
ref: "91e4d1a9c55b28bf93425d1f12faf410efc1e48d"
|
||||
resolved-ref: "91e4d1a9c55b28bf93425d1f12faf410efc1e48d"
|
||||
url: "https://github.com/Sayegh7/move_to_background"
|
||||
source: git
|
||||
version: "1.0.2"
|
||||
native_dio_adapter:
|
||||
dependency: "direct main"
|
||||
@@ -1329,11 +1401,12 @@ packages:
|
||||
qr_code_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: qr_code_scanner
|
||||
sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
path: "."
|
||||
ref: a2d31633d4744f72ada87cfa85d221358ab082af
|
||||
resolved-ref: a2d31633d4744f72ada87cfa85d221358ab082af
|
||||
url: "https://github.com/juliuscanute/qr_code_scanner"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
qr_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1731,6 +1804,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
ua_client_hints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ua_client_hints
|
||||
sha256: "1b8759a46bfeab355252881df27f2604c01bded86aa2b578869fb1b638b23118"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
universal_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
|
||||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 4.4.3+443
|
||||
version: 4.4.7+447
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
adaptive_theme: ^3.1.0 # done
|
||||
adaptive_theme: ^3.1.0
|
||||
app_links: ^6.3.3
|
||||
archive: ^4.0.7
|
||||
auto_size_text: ^3.0.0
|
||||
base32: ^2.1.3
|
||||
bip39: ^1.0.6 #done
|
||||
bip39: ^1.0.6
|
||||
bloc: ^9.0.0
|
||||
clipboard: ^0.1.3
|
||||
collection: ^1.18.0 # dart
|
||||
@@ -25,19 +24,41 @@ dependencies:
|
||||
dotted_border: ^3.1.0
|
||||
dropdown_button2: ^2.3.9
|
||||
email_validator: ^3.0.0
|
||||
ente_accounts:
|
||||
path: ../../packages/accounts
|
||||
ente_base:
|
||||
path: ../../packages/base
|
||||
ente_configuration:
|
||||
path: ../../packages/configuration
|
||||
ente_crypto_dart:
|
||||
git:
|
||||
url: https://github.com/ente-io/ente_crypto_dart.git
|
||||
ente_events:
|
||||
path: ../../packages/events
|
||||
ente_lock_screen:
|
||||
path: ../../packages/lock_screen
|
||||
ente_logging:
|
||||
path: ../../packages/logging
|
||||
ente_network:
|
||||
path: ../../packages/network
|
||||
ente_qr:
|
||||
path: plugins/qr
|
||||
ente_strings:
|
||||
path: ../../packages/strings
|
||||
ente_ui:
|
||||
path: ../../packages/ui
|
||||
event_bus: ^2.0.0
|
||||
expandable: ^5.0.1
|
||||
expansion_tile_card: ^3.0.0
|
||||
ffi: ^2.1.0
|
||||
figma_squircle: ^0.6.3
|
||||
file_picker: ^10.2.0
|
||||
# https://github.com/incrediblezayed/file_saver/issues/86
|
||||
file_saver: ^0.3.0
|
||||
file_picker: ^10.3.2
|
||||
file_saver: ^0.3.1
|
||||
fixnum: ^1.1.0
|
||||
fk_user_agent: ^2.1.0
|
||||
fk_user_agent: # no package updates on pub.dev
|
||||
git:
|
||||
url: https://github.com/flutter-fast-kit/fk_user_agent
|
||||
ref: 458046cd9a88924e5074d96ba45397219d53b230
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_animate: ^4.1.0
|
||||
@@ -47,13 +68,9 @@ dependencies:
|
||||
flutter_email_sender: ^7.0.0
|
||||
# revert to pub.dev when merged
|
||||
# https://github.com/pichillilorenzo/flutter_inappwebview/pull/2548
|
||||
flutter_inappwebview:
|
||||
git:
|
||||
url: https://github.com/pichillilorenzo/flutter_inappwebview.git
|
||||
path: flutter_inappwebview
|
||||
ref: 3e6c4c4a25340cd363af9d38891d88498b90be26
|
||||
flutter_inappwebview: ^6.1.4
|
||||
flutter_launcher_icons: ^0.14.1
|
||||
flutter_local_authentication:
|
||||
flutter_local_authentication: # linux fprintd fix is not published on pub.dev
|
||||
git:
|
||||
url: https://github.com/eaceto/flutter_local_authentication
|
||||
ref: 1ac346a04592a05fd75acccf2e01fa3c7e955d96
|
||||
@@ -69,6 +86,7 @@ dependencies:
|
||||
google_nav_bar: ^5.0.5 #supported
|
||||
gradient_borders: ^1.0.0
|
||||
http: ^1.1.0
|
||||
image: ^4.5.4
|
||||
intl: ^0.20.2
|
||||
io: ^1.0.4
|
||||
json_annotation: ^4.5.0
|
||||
@@ -77,7 +95,10 @@ dependencies:
|
||||
local_auth_darwin: ^1.2.2
|
||||
logging: ^1.0.1
|
||||
modal_bottom_sheet: ^3.0.0
|
||||
move_to_background: ^1.0.2
|
||||
move_to_background: # no package updates on pub.dev
|
||||
git:
|
||||
url: https://github.com/Sayegh7/move_to_background
|
||||
ref: 91e4d1a9c55b28bf93425d1f12faf410efc1e48d
|
||||
native_dio_adapter: ^1.4.0
|
||||
otp: ^3.1.1
|
||||
package_info_plus: ^8.0.2
|
||||
@@ -88,8 +109,11 @@ dependencies:
|
||||
pointycastle: ^3.7.3
|
||||
privacy_screen: ^0.0.6
|
||||
protobuf: ^4.1.0
|
||||
qr_code_scanner: ^1.0.1
|
||||
qr_flutter: ^4.1.0
|
||||
qr_code_scanner: # no package updates on pub.dev
|
||||
git:
|
||||
url: https://github.com/juliuscanute/qr_code_scanner
|
||||
ref: a2d31633d4744f72ada87cfa85d221358ab082af
|
||||
qr_flutter: ^4.1.0
|
||||
sentry: ^8.14.2
|
||||
sentry_flutter: ^8.14.2
|
||||
share_plus: ^11.0.0
|
||||
|
||||
22
mobile/apps/auth/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# melos_managed_dependency_overrides: ente_accounts,ente_base,ente_configuration,ente_events,ente_lock_screen,ente_logging,ente_network,ente_strings,ente_ui,ente_utils
|
||||
dependency_overrides:
|
||||
ente_accounts:
|
||||
path: ../../packages/accounts
|
||||
ente_base:
|
||||
path: ../../packages/base
|
||||
ente_configuration:
|
||||
path: ../../packages/configuration
|
||||
ente_events:
|
||||
path: ../../packages/events
|
||||
ente_lock_screen:
|
||||
path: ../../packages/lock_screen
|
||||
ente_logging:
|
||||
path: ../../packages/logging
|
||||
ente_network:
|
||||
path: ../../packages/network
|
||||
ente_strings:
|
||||
path: ../../packages/strings
|
||||
ente_ui:
|
||||
path: ../../packages/ui
|
||||
ente_utils:
|
||||
path: ../../packages/utils
|
||||