Compare commits

..

109 Commits

Author SHA1 Message Date
Prateek Sunal
29c67bd5dd fix: refactor Theme.of(context) to a variable 2025-08-22 02:27:35 +05:30
Prateek Sunal
da15842597 fix: deprecated normal functions for getting theme 2025-08-22 02:27:00 +05:30
Prateek Sunal
eb959046cd fix: all theme undefined errors 2025-08-22 01:27:16 +05:30
Prateek Sunal
1178ae26d0 fix: get things back 2025-08-21 23:54:47 +05:30
Prateek Sunal
4a2f5b4676 Merge remote-tracking branch 'origin/main' into optimize-widget-dependencies 2025-08-21 23:49:48 +05:30
Prateek Sunal
9fe15d7ff0 [mob][photos] remove generated locals (#6925)
## Description

We depended on intl_utils but that had problems + it was not
auto-generating things when running `flutter pub get`

Now we are using pure intl implementation of l10n, by which generated
code would be less.

- [x] Removes generated locals

## Tests
2025-08-21 22:27:09 +05:30
Prateek Sunal
28a2afe275 Merge remote-tracking branch 'origin/main' into remove-intl_utils 2025-08-21 22:26:54 +05:30
Aman Raj Singh Mourya
c072097c11 Add custom icons (#6924)
## Description
Adds custom SVG icons for "CoinTracing", "VHV Versicherungen (German
Insurance Company)" and "HR Document Box".
SVG sourced from official CoinTracking press assets.
SVG sourced from Wikipedia for VHV Versicherungen.
For HR Document Box was no svg available, so i used a png to svg
converter.

## Tests
2025-08-21 19:27:57 +05:30
Prateek Sunal
0fece5666b feat: optimize ente color scheme and text theme 2025-08-21 19:04:56 +05:30
Prateek Sunal
26e564aec2 fix: optimize theme dependencies for many files 2025-08-21 18:50:44 +05:30
Prateek Sunal
dd420a80a4 Merge remote-tracking branch 'origin/main' into remove-intl_utils 2025-08-21 18:08:31 +05:30
Prateek Sunal
3dc0620e18 chore: update flutter again for auth 2025-08-21 18:04:29 +05:30
Prateek Sunal
173d075f8b feat: remove generated locals 2025-08-21 18:04:20 +05:30
Daniel
48283282e5 Merge branch 'ente-io:main' into main 2025-08-21 14:30:49 +02:00
Laurens Priem
fa555c448f [mob][photos] Various similar images improvements (#6922)
## Description

Various improvements for similar images, mainly caching and UI changes.

## Tests

Tested in debug mode on my pixel phone.
2025-08-21 17:59:13 +05:30
laurenspriem
6f6770d677 Selection options 2025-08-21 17:58:43 +05:30
Daniel
0b894e9724 Update custom-icons.json 2025-08-21 14:21:02 +02:00
Daniel
0670550cb1 Add files via upload 2025-08-21 14:18:48 +02:00
Daniel
45783cf527 Update custom-icons.json 2025-08-21 14:07:32 +02:00
Daniel
1615779eb8 Add files via upload 2025-08-21 14:05:48 +02:00
Daniel
02e4c9d8fd Add files via upload 2025-08-21 14:00:23 +02:00
Daniel
6eab6457ee Update custom-icons.json 2025-08-21 13:34:14 +02:00
Daniel
25490a7238 Add files via upload 2025-08-21 13:30:49 +02:00
Neeraj
f519ff8a51 [auth] Option to scan image using native plugin (#6920)
## Description

## Tests
Tested on physical device (android and ios)
2025-08-21 16:20:45 +05:30
Neeraj Gupta
afebe1ade1 Clean up 2025-08-21 16:15:44 +05:30
laurenspriem
3862644dd5 Show info at top 2025-08-21 16:07:52 +05:30
Neeraj Gupta
274a7d207d Improve scanning 2025-08-21 15:59:53 +05:30
laurenspriem
add2f0c8de Debug option for full refresh 2025-08-21 15:34:05 +05:30
Neeraj Gupta
8e807616e0 Bump version 2025-08-21 15:29:08 +05:30
laurenspriem
70f4325c71 Refresh for small size 2025-08-21 15:18:05 +05:30
Neeraj Gupta
38ea2248b8 Show option on mobile only 2025-08-21 15:16:21 +05:30
laurenspriem
9600b26359 More stable sort 2025-08-21 15:10:40 +05:30
Neeraj Gupta
5b3e996aaa Option to scan image using native plugin 2025-08-21 15:08:11 +05:30
laurenspriem
4d4cce091f Fix json decoding issue 2025-08-21 14:13:49 +05:30
Neeraj
aaca140d1b [mobile] Use same lint rule file (#6917)
## Description
Had verified that hash for these rules were same.
## Tests
2025-08-21 14:06:37 +05:30
Neeraj Gupta
596ffcd4c4 [mobile] Use same lint rule file 2025-08-21 14:04:04 +05:30
laurenspriem
41ef85a294 Add scroll bars 2025-08-21 13:08:51 +05:30
Neeraj
f722d82835 [server]: add one click verify button for verification email (#5654)
OG (ott.html):

<img width="701" height="590" alt="image"
src="https://github.com/user-attachments/assets/80b926d1-c65f-44a8-9de4-7b591258bf3c"
/>



New (ott_mobile.html):

<img width="642" height="811" alt="image"
src="https://github.com/user-attachments/assets/aa18a778-1161-4b4e-ad82-cf472da06ff7"
/>
2025-08-21 12:44:24 +05:30
Neeraj Gupta
cbb3096534 use mobile ott template for only photos 2025-08-21 12:41:56 +05:30
laurenspriem
f635e1e856 Merge branch 'main' into smart_dedupe 2025-08-21 12:22:29 +05:30
laurenspriem
c6734a5cb7 More logging 2025-08-21 12:15:37 +05:30
laurenspriem
e26b4796d3 Match to closest group 2025-08-21 12:04:31 +05:30
laurenspriem
99c0194c0f Check cache parameters 2025-08-21 11:54:10 +05:30
laurenspriem
e824c02d7f Fix bug 2025-08-21 11:45:08 +05:30
laurenspriem
a11f66b51d Caching and partial compute logic for similar files calculation 2025-08-21 11:35:42 +05:30
laurenspriem
f202fef266 JSON caching of similar files 2025-08-21 11:35:12 +05:30
Neeraj
ff8cfd3e87 [auth] Additional import option (#6916)
## Description

## Tests
2025-08-21 11:22:04 +05:30
Neeraj Gupta
431ab7fcc7 [auth] Additional import option 2025-08-21 11:11:48 +05:30
Ashil
2ac1d58dac [mob][photos] Fix jank when scrolling gallery when lot of selected items are in view (#6913)
## Description

Using `ColorFiltered` with `Blendmode.darken` introduces a performance
[issue](https://github.com/flutter/flutter/issues/174118) with flutter's
new rendering engine Impeller.

The fix uses an alternative method to that maintains the same UI
appearance as before.
2025-08-20 19:09:46 +05:30
ashilkn
5533e6a71d Fix performance issue (jank) when scrolling gallery when lot of selected items in view
This was only bad enough to be noticed on one device (Samsung A54) out of few
2025-08-20 18:46:32 +05:30
laurenspriem
a8ae0727a8 Fix potential duplicates 2025-08-20 18:24:21 +05:30
Aman Raj Singh Mourya
6955788724 Add custom icon for Mobile01 (#6908)
Custom icon for [Mobile01](https://www.mobile01.com/)

- Add optimised SVG file
- Add entry to custom-icons.json
2025-08-20 15:49:42 +05:30
Neeraj Gupta
f6dd35f5e7 Update templates 2025-08-20 15:11:28 +05:30
Neeraj Gupta
9148916d88 Merge remote-tracking branch 'origin/main' into ott_email_one_click 2025-08-20 15:02:57 +05:30
Neeraj
68545f8947 [auth] add import from gallery using zxing2 (#6909)
This pr implements the feature to add a 2FA account by importing a QR
code image from the device gallery..
Adds a new "Import from gallery" button to the Floating Action Button
menu.

The button's text is localized, and its styling is consistent with the
app's theme.

How to Test:

    Open the FAB menu and tap "Import from gallery".
    
1) Test with a valid 2FA QR code image. Expected: The account gets added
successfully.
2) Test with an image that has no QR code. Expected: A "No QR code
found" toast message appears.
3) Test with a QR code of plain text (like "hello "). Expected: An
"Invalid QR Code" dialog appears.
2025-08-20 14:44:02 +05:30
Neeraj
76c5c12c53 [mobile] Setup melos (#6907)
## Description

```
dart pub global activate melos
```
Run following command to get all dependencies for projects
```
melos bootstrap
```


## Tests
2025-08-20 14:36:10 +05:30
Dxball ☕
8d749a2dc8 Add custom icon fo Mobile01 2025-08-20 09:01:14 +00:00
a5xwin
56af818482 add qr code scanning from gallery using zxing2 2025-08-20 14:28:41 +05:30
Neeraj
fc1096c985 [mob] Include mobile flag during ott (#6906)
## Description
Need this to identify which OTT template to send from backend.
Ref: https://github.com/ente-io/ente/pull/5654
## Tests
2025-08-20 14:25:22 +05:30
Neeraj Gupta
88260a05e3 Setup melos 2025-08-20 14:14:35 +05:30
Neeraj Gupta
4b5f91a428 [mob] Send ismobile flag 2025-08-20 13:14:59 +05:30
Neeraj
29b12fc6b5 [docs] Lint (#6905)
## Description

## Tests
2025-08-20 11:01:44 +05:30
Neeraj Gupta
c3eec71d60 [docs] Lint 2025-08-20 11:01:25 +05:30
Neeraj
2fccdee0d6 [server] Add optional TLS/SSL encryption for SMTP (#6863)
## Description

Implement TLS/SSL encryption for sending emails via SMTP. When an SMTP
provider explicitly requires TLS/SSL communication the current
implementation runs in a timeout and fails. A new configuration
parameter for smtp was added to enable TLS/SSL communication.

This would solve #5958 

## Tests

I built a local docker image of my branch. The email provider I was
using is mailbox.org and using the tls configuration. Registering a new
user then resulted in a sent email containing the verification code.

I did not test a setup without TLS/SSL.
2025-08-20 11:00:36 +05:30
Neeraj
8f053e7a7b [m][photos] Fix for duplicate entries for local file (#6904)
## Description

## Tests
2025-08-20 10:42:41 +05:30
Neeraj
de7291f5d4 [mobile][photos] don't show lock screen on deep link (#6899)
## Description

Fixes the clicking on widget behavior

## Tests
2025-08-20 10:06:06 +05:30
Neeraj Gupta
e09c952198 Fix for duplicate enteries for file 2025-08-20 10:01:51 +05:30
laurenspriem
28a842b006 Show size 2025-08-20 09:51:27 +05:30
laurenspriem
19eb342f59 Move internal things to debug mode only 2025-08-20 09:47:36 +05:30
laurenspriem
2b82c79be9 Merge branch 'main' into smart_dedupe 2025-08-20 09:13:43 +05:30
Prateek Sunal
86a9dee49d fix: add note, revert old code, remove default redirect 2025-08-20 00:25:33 +05:30
Vishnu Mohandas
33b1a7e4f8 [mob] Remove generated code (#6901) 2025-08-19 20:22:51 +05:30
vishnukvmd
de783e91dc Ignore build 2025-08-19 20:22:22 +05:30
vishnukvmd
554c8f4b7a Remove noise 2025-08-19 20:22:11 +05:30
Vishnu Mohandas
f438142646 [locker] Icon (#6900) 2025-08-19 19:00:07 +05:30
vishnukvmd
aa6c010562 Update Android icon 2025-08-19 18:59:33 +05:30
vishnukvmd
2830c89bde Set icons 2025-08-19 18:58:21 +05:30
Prateek Sunal
4de530b882 fix: don't show lock sreen if not required 2025-08-19 18:27:33 +05:30
Vishnu Mohandas
62d7c87dc7 [locker] Update deps (#6898) 2025-08-19 18:23:51 +05:30
vishnukvmd
3a1b6dbf15 [locker] Update deps 2025-08-19 18:23:19 +05:30
laurenspriem
58182cc8ab Remove slider interface in release 2025-08-19 17:11:12 +05:30
Laurens Priem
5e0bba390b [mob][photos] Decoded image minor refactor (#6897)
## Description

Minor refactor (no functional change) in the way we're decoding and
returning images for indexing.
2025-08-19 16:42:24 +05:30
Vishnu Mohandas
df6a3b94db [mobile][locker] Add Drawer & Setting section in locker (#6895) 2025-08-19 15:35:41 +05:30
AmanRajSinghMourya
72b7e12768 Merge branch 'main' into drawer 2025-08-19 15:24:29 +05:30
AmanRajSinghMourya
3a7d82a799 Add setting section 2025-08-19 14:58:15 +05:30
Neeraj
5f1cfb9ba5 [server] Fail request on customDomain mismatch (#6893)
## Description

## Tests
2025-08-19 14:23:00 +05:30
Neeraj Gupta
298e3695c7 [server] Fail request on customDomain mismatch 2025-08-19 12:34:31 +05:30
Vishnu Mohandas
621713d0b4 [mob] Remove unused import (#6892) 2025-08-19 12:33:15 +05:30
vishnukvmd
34813d2fae [mob] Remove unused import 2025-08-19 12:32:58 +05:30
laurenspriem
bb177bc3f6 Merge branch 'main' into smart_dedupe 2025-08-19 11:47:19 +05:30
Vishnu Mohandas
d8e4418d78 Remove ignore (#6890) 2025-08-19 11:43:36 +05:30
vishnukvmd
9771a5bc5d Remove ignore 2025-08-19 11:41:44 +05:30
laurenspriem
65f7e3f6c6 Exclude rust_builder in linter 2025-08-19 11:39:03 +05:30
Neeraj
aa4207f878 [auth] New translations (#6877)
New translations from
[Crowdin](https://crowdin.com/project/ente-authenticator-app)
2025-08-19 10:13:12 +05:30
laurenspriem
37c1d0f6a8 Unify UI 2025-08-18 18:23:14 +05:30
laurenspriem
be1bf28cd8 Add cacheExtend for smoother scroll 2025-08-18 17:25:44 +05:30
laurenspriem
cad8613e81 Remove "select extra" button 2025-08-18 10:28:57 +05:30
laurenspriem
b46e51f64d Prefer smaller names 2025-08-18 10:25:06 +05:30
laurenspriem
e6bf64548c Use cached files 2025-08-18 10:21:28 +05:30
laurenspriem
5729e0cf3e no shared or hidden files 2025-08-18 10:11:04 +05:30
laurenspriem
b353539328 Sort by highest size first 2025-08-18 09:54:31 +05:30
Crowdin Bot
f2049ac7fa New Crowdin translations by GitHub Action 2025-08-18 01:18:04 +00:00
Kilian Hohm
cf938eca91 Add CLI command to send a test email via admin API 2025-08-16 10:33:23 +02:00
Kilian Hohm
a3d3ee24f8 Document optional TLS/SSL encryption for sending emails via SMTP 2025-08-15 15:48:59 +02:00
Kilian Hohm
6b37cc46a5 Add optional TLS/SSL encryption for sending emails via SMTP 2025-08-15 15:48:41 +02:00
laurenspriem
ad39694026 report changes at correct place 2025-08-14 19:06:03 +05:30
anandbaburajan
1752192688 [email]: different ott templates for with and without verify btn 2025-06-09 19:21:49 +05:30
anandbaburajan
873ee3ac14 [email]: different views for mobile and desktop 2025-04-18 16:54:21 +05:30
anandbaburajan
cfce2d00f5 [email]: add one click verify button for verification email 2025-04-18 13:45:04 +05:30
589 changed files with 9022 additions and 70470 deletions

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
View 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

View 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

View File

@@ -1 +0,0 @@
surprise/

View File

@@ -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"
},
@@ -1773,6 +1790,11 @@
"uollet.com.br"
]
},
{
"title": "VHV",
"slug": "vhv",
"altNames": ["VHV", "VHV Versicherung"]
},
{
"title": "Vikunja"
},

View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View 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

View 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

View File

@@ -39,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
@@ -126,6 +128,8 @@ PODS:
- sqlite3/perf-threadsafe
- sqlite3/rtree
- SwiftyGif (5.4.5)
- ua_client_hints (1.4.1):
- Flutter
- url_launcher_ios (0.0.1):
- Flutter
@@ -134,6 +138,7 @@ DEPENDENCIES:
- 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`)
@@ -158,6 +163,7 @@ DEPENDENCIES:
- sodium_libs (from `.symlinks/plugins/sodium_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:
@@ -180,6 +186,8 @@ EXTERNAL SOURCES:
: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:
@@ -228,6 +236,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
sqlite3_flutter_libs:
: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"
@@ -238,6 +248,7 @@ SPEC CHECKSUMS:
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
ente_qr: f39434aa69ea0e71047b49316365b2737f8a20aa
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
@@ -268,6 +279,7 @@ SPEC CHECKSUMS:
sqlite3: 3e82a2daae39ba3b41ae6ee84a130494585460fc
sqlite3_flutter_libs: 2c48c4ee7217fd653251975e43412250d5bcbbe2
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
ua_client_hints: aeabd123262c087f0ce151ef96fa3ab77bfc8b38
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
PODFILE CHECKSUM: 78f002751f1a8f65042b8da97902ba4124271c5a

View File

@@ -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": "Ψηφία"
}

View File

@@ -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."
}

View File

@@ -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?",

View File

@@ -39,7 +39,9 @@ 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';
@@ -48,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 BaseHomePage {
class HomePage extends BaseHomePage {
const HomePage({super.key});
@override
@@ -62,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>();
@@ -288,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(
@@ -745,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,
),
],
);
}

View File

@@ -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;
}
}
}

View 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;
}

View File

@@ -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';
}
}

View 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'

View 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

View 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.

View File

@@ -0,0 +1 @@
include: ../../analysis_options.yaml

View 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
}
}
}
}

View File

@@ -0,0 +1 @@
rootProject.name = 'ente_qr'

View File

@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.ente.auth.ente_qr">
</manifest>

View File

@@ -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)
}
}

View 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"
])
}
}
}

View 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

View 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);
}
}

View 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');
}
}
}

View File

@@ -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.');
}
}

View 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"

View 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

View 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');
});
}

View File

@@ -451,6 +451,13 @@ packages:
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:
@@ -466,7 +473,7 @@ packages:
source: path
version: "1.0.0"
ente_utils:
dependency: transitive
dependency: "direct overridden"
description:
path: "../../packages/utils"
relative: true
@@ -532,10 +539,10 @@ 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:
@@ -951,7 +958,7 @@ packages:
source: hosted
version: "0.1.0"
image:
dependency: transitive
dependency: "direct main"
description:
name: image
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"

View File

@@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 4.4.6+446
version: 4.4.7+447
publish_to: none
environment:
@@ -41,6 +41,8 @@ dependencies:
path: ../../packages/logging
ente_network:
path: ../../packages/network
ente_qr:
path: plugins/qr
ente_strings:
path: ../../packages/strings
ente_ui:
@@ -50,7 +52,7 @@ dependencies:
expansion_tile_card: ^3.0.0
ffi: ^2.1.0
figma_squircle: ^0.6.3
file_picker: ^10.2.0
file_picker: ^10.3.2
file_saver: ^0.3.1
fixnum: ^1.1.0
fk_user_agent: # no package updates on pub.dev
@@ -84,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

View 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

View File

@@ -1 +1,7 @@
# soon.
# Ente Locker
## TODOs
Refactor and merge
- [ ] Verify correctness for `PackageInfoUtil.getPackageName()` on Linux and Windows
- [ ] Update `file_url.dart` to download only via CF worker when necessary

View File

@@ -1,72 +1 @@
# 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
include: ../../analysis_options.yaml

View File

@@ -8,7 +8,7 @@
<application
android:label="Ente Locker"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/icon_blue">
<activity
android:name=".MainActivity"
android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#1071ff</color>
</resources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -75,9 +75,9 @@ PODS:
- FlutterMacOS
- privacy_screen (0.0.1):
- Flutter
- 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
@@ -209,7 +209,7 @@ SPEC CHECKSUMS:
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImage: f29024626962457f3470184232766516dee8dfea
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f

View File

@@ -622,7 +622,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = IconBlue;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -929,7 +929,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = IconBlue;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -956,7 +956,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = IconBlue;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1 @@
{"images":[{"size":"20x20","idiom":"iphone","filename":"IconBlue-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"IconBlue-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"IconBlue-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"IconBlue-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"IconBlue-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"IconBlue-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"IconBlue-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"IconBlue-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"IconBlue-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconBlue-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconBlue-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"IconBlue-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"IconBlue-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"IconBlue-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"IconBlue-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"IconBlue-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"IconBlue-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"IconBlue-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"IconBlue-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"IconBlue-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"IconBlue-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"IconBlue-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"IconBlue-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"IconBlue-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"IconBlue-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Some files were not shown because too many files have changed in this diff Show More