Compare commits
242 commits
Author | SHA1 | Date | |
---|---|---|---|
ae2a6b8b32 | |||
dce3c916a2 | |||
58141fe646 | |||
dd5abc48f9 | |||
bf1f1e3295 | |||
e7463ffaef | |||
ae454abb3a | |||
34bcd2ee92 | |||
d2485a91cd | |||
e8c8808c0e | |||
b544003941 | |||
5668c15547 | |||
7f145f6960 | |||
d0dd798008 | |||
f16ee08079 | |||
a4a3c2e5dc | |||
a5e9597207 | |||
f460c68339 | |||
c7af6a874d | |||
e0973735d8 | |||
dc715e38d1 | |||
eb1d949af8 | |||
4a715aba48 | |||
c48c16d63d | |||
57db10e912 | |||
a4defff982 | |||
3ae2c37813 | |||
d9b33feac9 | |||
941b7c81ad | |||
5bc02638f7 | |||
477e557d31 | |||
c917e478a6 | |||
8b96610fa6 | |||
df40808bd7 | |||
dfc5772b93 | |||
3d6ae1a9ef | |||
e49dd86347 | |||
4cd6165d9b | |||
e661171fd2 | |||
c99df89cb0 | |||
03d61dcdd0 | |||
f2d1083620 | |||
b5694ae9c6 | |||
9dd84b20a9 | |||
8bb310fb40 | |||
47987b8dad | |||
3d68dcefab | |||
b51131de8f | |||
7e871bb4b1 | |||
88b9d81bc2 | |||
7c7befe071 | |||
2461a02f9d | |||
15104d7e40 | |||
733f09baab | |||
1758eeed18 | |||
c9764e780b | |||
3754d71dec | |||
8904d49eb2 | |||
0e7bc55bef | |||
089d07846d | |||
3f64f86acf | |||
7c91d8b6ca | |||
df1b038782 | |||
c9ef125abb | |||
5a8750b877 | |||
8c10e42c0f | |||
96d8cbea99 | |||
685b140135 | |||
f51e781a9f | |||
16c1a597fc | |||
71ac72a6cc | |||
0bc3ae1da8 | |||
fc5b8783d3 | |||
5492fed0b5 | |||
30810868b2 | |||
a889a6fbee | |||
36e5bcab1f | |||
3b06c2b80b | |||
e4cfa81672 | |||
2837054383 | |||
4deb9b92cb | |||
b0edda0432 | |||
886632957d | |||
264609dff3 | |||
d87796f47b | |||
42f40d52d2 | |||
292660a970 | |||
51e2c476c7 | |||
44cc8bac28 | |||
9b571fb1a4 | |||
d63ef288eb | |||
549b218922 | |||
f61c5a4a1b | |||
a06bbf6751 | |||
e980f1ae53 | |||
66a5f4e9af | |||
39b1465efc | |||
22d096ae43 | |||
08d935284d | |||
6b87873cc7 | |||
41987331b0 | |||
f19d23a757 | |||
67076779a3 | |||
6d302c4392 | |||
53cd587a42 | |||
769dd7e513 | |||
a3ee146e25 | |||
5e9947969e | |||
689c8b4408 | |||
7cee219b19 | |||
803cd11cc4 | |||
a99662af3c | |||
3d11fde4e9 | |||
43cb6472f2 | |||
67dafa615f | |||
fc0fe31b70 | |||
0e4dcbde22 | |||
65a0a294a6 | |||
548d02362b | |||
c50e9ec7ba | |||
e5518b4ade | |||
0b093302ec | |||
4114feb56e | |||
d8c9027509 | |||
e1075f5725 | |||
b49f10c875 | |||
494420cbd3 | |||
aedc7f2bbb | |||
9ac363070b | |||
580099856f | |||
d56d717de0 | |||
3cf96ad5ab | |||
26d0e0851b | |||
c772d950e2 | |||
0630035e45 | |||
3fb46fc766 | |||
53c65ce54f | |||
9a1db51f23 | |||
664b734544 | |||
0e69e904b4 | |||
09b608a168 | |||
091e344831 | |||
0f9e8b398f | |||
88d85257a7 | |||
06566d3d6e | |||
35bdd06f72 | |||
e59164e803 | |||
eec38b455c | |||
ee6230399a | |||
31d91bafc1 | |||
82f9ab5535 | |||
66493cff91 | |||
210c3e7aa4 | |||
d6b645112e | |||
713d8f57be | |||
bc777d4826 | |||
ad9a3a15f5 | |||
dbb4929939 | |||
adb55fd73b | |||
004133f0c0 | |||
67903d1331 | |||
0a0fbe039b | |||
6ef8a9daa0 | |||
47b45a9084 | |||
06dab63e05 | |||
9c331cf6d3 | |||
40451dbabc | |||
6bb4c177e2 | |||
81eb1af2bd | |||
3e7bc379f1 | |||
e9929bd3c3 | |||
9386661adb | |||
46a6df9f80 | |||
577f9cd42c | |||
d8d89167a9 | |||
6d75672add | |||
fc88d5c22f | |||
a2ee915463 | |||
6e5fa0eaa8 | |||
8c3bf06b87 | |||
5e9ac38ca0 | |||
a5dab51765 | |||
a65c7d9253 | |||
bac39aebdf | |||
b55e932204 | |||
f9a2bb0df7 | |||
a0789e6883 | |||
2e7778926a | |||
c2c8ffc5c5 | |||
b18d63ce1e | |||
06db805f82 | |||
3f927328f0 | |||
8d11584811 | |||
db9fef6798 | |||
6fedb8c661 | |||
d3e54eb1d5 | |||
874d734c80 | |||
a6cdd407c0 | |||
583520ea7c | |||
77fee6ae32 | |||
cd07b2fba2 | |||
011113d72b | |||
7ef179906d | |||
c8007e8415 | |||
a6851c5430 | |||
80818f5eda | |||
3625461b57 | |||
830deab2e1 | |||
15e1607072 | |||
71dcab97be | |||
3c06e3b29f | |||
2b1d8939d2 | |||
927be613c9 | |||
80f20f6032 | |||
c82c5f8dc5 | |||
c16242d340 | |||
207985c745 | |||
ccb780be50 | |||
35e957a049 | |||
c337e8a7b8 | |||
16636d9f7c | |||
aa07d61a3c | |||
9ec215cbf4 | |||
79593bd288 | |||
707520b44c | |||
57a502f0c3 | |||
4fdcf54dc0 | |||
c1255dcab5 | |||
b2101e2327 | |||
9e740d31d4 | |||
f3ec810f8f | |||
d13769f30f | |||
b5301a85f8 | |||
6e215ff935 | |||
dbe1604329 | |||
9b440ee63c | |||
c0a2e8d569 | |||
06eb990eea | |||
230de7fe40 | |||
c5da7ec84d | |||
ad10edffd7 | |||
e623cef713 |
104 changed files with 4235 additions and 1586 deletions
6
.editorconfig
Normal file
6
.editorconfig
Normal file
|
@ -0,0 +1,6 @@
|
|||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
21
.forgejo/workflows/build.yaml
Normal file
21
.forgejo/workflows/build.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
on: [ push ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ghcr.io/cirruslabs/flutter:3.24.4
|
||||
steps:
|
||||
- name: Prepare requirements
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y nodejs npm git
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build
|
||||
run: |
|
||||
flutter doctor
|
||||
flutter pub get
|
||||
flutter pub outdated
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
flutter analyze --no-pub --no-current-package lib/
|
||||
flutter build apk --debug
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,6 +1,8 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.lock
|
||||
!Gemfile.lock
|
||||
!pubspec.lock
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
|
@ -16,6 +18,7 @@
|
|||
*.iws
|
||||
.idea/
|
||||
**/out/**
|
||||
.run/
|
||||
|
||||
# Visual Studio Code related
|
||||
.vscode/
|
||||
|
|
90
CHANGELOG.md
90
CHANGELOG.md
|
@ -1,5 +1,95 @@
|
|||
# CHANGELOG
|
||||
|
||||
## 1.6.4+22 - 2024/11/01
|
||||
* Dependency updates
|
||||
* Internal build updates
|
||||
|
||||
## 1.6.3+21
|
||||
* Fixed not receiving share requests from other applications
|
||||
|
||||
## 1.6.2+20
|
||||
* Updated internal dependencies
|
||||
* Moved progress indicator of _Show Configuration_ into the underlying button
|
||||
* Bumped Android minSdk to `30` (Android 11)
|
||||
* Bumped Android targetSdk to `34` (Android 14)
|
||||
* Fixed permission service not handling Android SDK 33 correctly
|
||||
* Fixed permission service not being started during application start
|
||||
|
||||
## 1.6.1+19
|
||||
* Updated internal dependencies
|
||||
|
||||
## 1.6.0+18
|
||||
* Fixed input colors in login view when using dark theme
|
||||
* Added removal of individual files selected for upload
|
||||
* Added size for individual files selected for upload
|
||||
* Replaced intent sharing library with `flutter_sharing_intent`
|
||||
* Added proper linting to project
|
||||
|
||||
## 1.5.1+17
|
||||
* Fixed white background button in AppBar when light theme enabled
|
||||
* Cleaned up login screen and added icon
|
||||
|
||||
## 1.5.0+16
|
||||
* Switched to Material You defaulting to blue swatch colors respecting dark mode
|
||||
* Switched to Material You navigation bar and removed unsupported swipe navigation
|
||||
* Increased target SDK to `33`
|
||||
* Increased dart to `>= 2.18.5`
|
||||
* Indicate configuration loading in profile view
|
||||
* Switched linked git repository away from GitHub
|
||||
* Updated internal dependencies
|
||||
|
||||
## 1.4.2+15
|
||||
* Minor cleanup
|
||||
* Added external drone CI
|
||||
* Updated to Android embedding v2
|
||||
* Updated to Gradle 7
|
||||
* Upgraded internal dependencies to latest versions
|
||||
|
||||
## 1.4.1+14
|
||||
* Fixed opening links
|
||||
|
||||
## 1.4.0+13
|
||||
* Increased target SDK to `30`
|
||||
* Upgraded to Dart `2.15.1`
|
||||
* Upgraded to use null-safety
|
||||
* Upgraded internal dependencies to latest versions
|
||||
* Replaced `share` with `share_plus`
|
||||
|
||||
## 1.3.3+12
|
||||
* Automatically switch to initial tab when coming from the share menu
|
||||
* Upgraded internal dependencies
|
||||
|
||||
## 1.3.2+11
|
||||
* Add a slash to copied URLs
|
||||
* Expand item when pressing on the card's title in history view
|
||||
|
||||
## 1.3.1+10
|
||||
* Added gesture detection for tab bar
|
||||
* Disable upload when no files have been attached or upload text input is empty
|
||||
* Added version information to about view
|
||||
* Minor refactor regarding state management
|
||||
|
||||
## 1.3.0+9
|
||||
* Allow API key login
|
||||
* Revamp profile view
|
||||
* Adapt color of tab bar text and use outlined icons when active
|
||||
* Suffix the API key comment with UNIX timestamp when credential login is used
|
||||
* Fixed an error when logging out and logging back in again in the history view
|
||||
* Minor code refactor
|
||||
|
||||
## 1.2.2+8
|
||||
* Adapt status bar color to match app's theme
|
||||
|
||||
## 1.2.1+7
|
||||
* Improve visual differences between the Upload tab and the upload button
|
||||
* Improved visuals in bottom tab bar
|
||||
* Updated dependencies and build with Flutter > 2 for the first time
|
||||
* Updated to new button styles for New Material theme proposed by Google
|
||||
|
||||
## 1.2.0+6
|
||||
* Only copy multipaste link if multi box checked
|
||||
* Add copy to clipboard shortcut in history view
|
||||
|
||||
## 1.1.0+5
|
||||
* Replace API key login with username and password login: a valid API key will automatically be created on login
|
||||
|
||||
|
|
|
@ -404,6 +404,14 @@ where to find the applicable terms.
|
|||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
7.1 Exceptions
|
||||
|
||||
7.1.1 As additional permission under section 7, you are allowed to distribute
|
||||
the software through an app store, even if that store has restrictive
|
||||
terms and conditions that are incompatible with the GPL, provided that the
|
||||
source is also available under the GPL with or without this permission through
|
||||
a channel without those restrictive terms and conditions.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
|
|
45
README.md
45
README.md
|
@ -1,6 +1,14 @@
|
|||
# README
|
||||
|
||||
A mobile flutter app for [FileBin](https://github.com/Bluewind/filebin).
|
||||
A mobile flutter app for [FileBin](https://git.server-speed.net/users/flo/filebin/).
|
||||
|
||||
Available on the [Play Store](https://play.google.com/store/apps/details?id=de.varakh.fbmobile) and
|
||||
[IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.varakh.fbmobile/).
|
||||
|
||||
The main git repository is hosted at **[https://git.myservermanager.com/varakh/fbmobile](https://git.myservermanager.com/varakh/fbmobile)**.
|
||||
Other repositories are mirrors and pull requests, issues, and planning are managed there.
|
||||
|
||||
Contributions are very welcome!
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
@ -27,13 +35,6 @@ This project is a starting point for a Flutter application.
|
|||
|
||||
Start by installing dependencies and generating entities!
|
||||
|
||||
### Working versions for SDK
|
||||
|
||||
```
|
||||
[✓] Flutter (Channel stable, 1.22.6, on Linux, locale en_US.UTF-8)
|
||||
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
* Run `flutter packages pub get` in project root folder to get dependencies or open the `pubspec.yaml` and click on the buttons provided by the IDE plugins
|
||||
|
||||
|
@ -94,7 +95,7 @@ profiles. They're stored in a separate git repository and are encrypted.
|
|||
|
||||
You need access to the git repository in which those private files reside.
|
||||
|
||||
#### Usage
|
||||
#### Usage / doing the actual release
|
||||
|
||||
Go into the platform directory you want to build for, e.g. `ios/` or `android/` and then look into the
|
||||
`Fastlane` file which lanes are present. Run a lane via `fastlane <platform> <lane>`, e.g. use the
|
||||
|
@ -104,13 +105,37 @@ following to build for Android `fastlane android build`.
|
|||
|
||||
##### Android
|
||||
|
||||
Use `fastlane android beta` to build and upload a new beta version to the Play Store.
|
||||
It's recommended you set up `fastlane` via `bundler` (you need this to be installed on your machine).
|
||||
|
||||
Go into the `android/` sub-directory of the project
|
||||
|
||||
```shell
|
||||
bundle config set --local path 'vendor/bundle'
|
||||
bundle install
|
||||
|
||||
# update fastlane when needed
|
||||
bundle update fastlane
|
||||
|
||||
# build only
|
||||
bundle exec fastlane android build
|
||||
|
||||
# deploy (push BETA to app store)
|
||||
bundle exec fastlane android beta
|
||||
|
||||
# deploy (push to app store)
|
||||
bundle exec fastlane android deploy
|
||||
|
||||
# deploy (build signed fdroid large bundle [no target and abi split])
|
||||
bundle exec fastlane android build_production_fdroid
|
||||
```
|
||||
|
||||
##### iOS
|
||||
|
||||
For iOS you need to execute `fastlane ios build` before uploading to testflight with
|
||||
`fastlane ios beta`.
|
||||
|
||||
Probably do the same Ruby/fastlane setup as mentioned under the _Android_ section.
|
||||
|
||||
### Release manually (not recommended)
|
||||
|
||||
See the following links on how to setup:
|
||||
|
|
30
analysis_options.yaml
Normal file
30
analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
|||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
library_private_types_in_public_api: false
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
2
android/.gitignore
vendored
2
android/.gitignore
vendored
|
@ -5,3 +5,5 @@ gradle-wrapper.jar
|
|||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.bundle
|
||||
vendor/
|
||||
|
|
222
android/Gemfile.lock
Normal file
222
android/Gemfile.lock
Normal file
|
@ -0,0 +1,222 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.1000.0)
|
||||
aws-sdk-core (3.211.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.95.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.169.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.10.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.1)
|
||||
fastlane (2.225.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.31.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.7.1)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-storage (1.47.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.31.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.7)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.5)
|
||||
jwt (2.9.3)
|
||||
base64
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.2.1)
|
||||
nkf (0.2.0)
|
||||
optparse (0.5.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (6.0.1)
|
||||
rake (13.2.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.9)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.27.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.16
|
|
@ -1,3 +1,9 @@
|
|||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
|
@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
|
|||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
|
@ -21,9 +22,6 @@ if (flutterVersionName == null) {
|
|||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
|
@ -31,7 +29,9 @@ if (keystorePropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 34
|
||||
|
||||
namespace "de.varakh.fbmobile"
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
|
@ -39,8 +39,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "de.varakh.fbmobile"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
minSdkVersion 30
|
||||
targetSdkVersion 34
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
|
|
@ -3,6 +3,17 @@
|
|||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
@ -6,50 +6,46 @@
|
|||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:name="${applicationName}"
|
||||
android:label="FileBin"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
@ -67,6 +63,18 @@
|
|||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
package de.varakh.fbmobile;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
@Override
|
||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
|
|
|
@ -3,6 +3,17 @@
|
|||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
@ -1,20 +1,7 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// TODO switch to 4.0.1 again: https://github.com/flutter/flutter/issues/58479
|
||||
// TODO switch to 4.0.1 again: https://github.com/miguelpruivo/flutter_file_picker/issues/545
|
||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +13,6 @@ subprojects {
|
|||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
|
|
@ -11,6 +11,11 @@ platform :android do
|
|||
sh("#{ENV['PWD']}/fastlane/buildAndroidProduction.sh")
|
||||
end
|
||||
|
||||
desc "Build Production fdroid"
|
||||
lane :build_production_fdroid do
|
||||
sh("#{ENV['PWD']}/fastlane/buildAndroidProductionFdroid.sh")
|
||||
end
|
||||
|
||||
desc "Build"
|
||||
lane :build do
|
||||
sh("#{ENV['PWD']}/fastlane/buildAndroid.sh")
|
||||
|
|
|
@ -1,54 +1,80 @@
|
|||
fastlane documentation
|
||||
================
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
Install _fastlane_ using
|
||||
```
|
||||
[sudo] gem install fastlane -NV
|
||||
```
|
||||
or alternatively using `brew install fastlane`
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
## Android
|
||||
|
||||
### android build_debug
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android build_debug
|
||||
```
|
||||
fastlane android build_debug
|
||||
```
|
||||
|
||||
Build Debug
|
||||
|
||||
### android build_production
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android build_production
|
||||
```
|
||||
fastlane android build_production
|
||||
```
|
||||
|
||||
Build Production
|
||||
|
||||
### android build_production_fdroid
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android build_production_fdroid
|
||||
```
|
||||
|
||||
Build Production fdroid
|
||||
|
||||
### android build
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android build
|
||||
```
|
||||
fastlane android build
|
||||
```
|
||||
|
||||
Build
|
||||
|
||||
### android alpha
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android alpha
|
||||
```
|
||||
fastlane android alpha
|
||||
```
|
||||
|
||||
Deploy a new version to the Google Play as Alpha
|
||||
|
||||
### android beta
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android beta
|
||||
```
|
||||
fastlane android beta
|
||||
```
|
||||
|
||||
Deploy a new version to the Google Play as Beta
|
||||
|
||||
### android deploy
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android deploy
|
||||
```
|
||||
fastlane android deploy
|
||||
```
|
||||
|
||||
Deploy a new version to the Google Play
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
|
||||
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
|
||||
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||
|
||||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
|
|
9
android/fastlane/buildAndroidProductionFdroid.sh
Executable file
9
android/fastlane/buildAndroidProductionFdroid.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
cd ../../;
|
||||
flutter clean && \
|
||||
flutter pub get &&
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs;
|
||||
|
||||
flutter build apk --release;
|
||||
flutter build apk --split-per-abi --release;
|
|
@ -1,4 +1,6 @@
|
|||
agpVersion=8.7.2
|
||||
kotlinVersion=1.7.10
|
||||
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.enableR8=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
include ':app'
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
def plugins = new Properties()
|
||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
||||
if (pluginsFile.exists()) {
|
||||
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins.each { name, path ->
|
||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
||||
include ":$name"
|
||||
project(":$name").projectDir = pluginDirectory
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "${agpVersion}" apply false
|
||||
id "org.jetbrains.kotlin.android" version "${kotlinVersion}" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
|
|
@ -1,132 +1,136 @@
|
|||
{
|
||||
"api": {
|
||||
"forbidden": "You're not allowed to access this resource",
|
||||
"bad_request": "Bad request: {reason}",
|
||||
"socket_error": "Not a valid host or no internet connection",
|
||||
"socket_timeout": "Request timed out - please ensure your internet connection is stable and that you're connecting to a valid FileBin instance and that it's not under heavy load",
|
||||
"general_rest_error": "An unknown error occurred during communication",
|
||||
"general_rest_error_payload": "An unknown error occurred during communication: {message}"
|
||||
},
|
||||
"app": {
|
||||
"title": "FileBin Mobile",
|
||||
"unknown_error": "An unknown error occurred, please try again"
|
||||
},
|
||||
"titles": {
|
||||
"login": "Login",
|
||||
"history": "History",
|
||||
"profile": "Profile",
|
||||
"about": "About",
|
||||
"upload": "Upload"
|
||||
},
|
||||
"tabs": {
|
||||
"login": "Login",
|
||||
"history": "History",
|
||||
"profile": "Profile",
|
||||
"upload": "Upload"
|
||||
},
|
||||
"upload": {
|
||||
"open_file_explorer": "Select...",
|
||||
"clear_temporary_files": "Clear",
|
||||
"text_to_be_pasted": "Text...",
|
||||
"upload": "Upload",
|
||||
"uploading_now": "Uploading...",
|
||||
"file_explorer_open": "Selecting files...",
|
||||
"uploaded": "Uploaded! Copied links to clipboard.",
|
||||
"dismiss": "Dismiss",
|
||||
"multipaste": "multipaste",
|
||||
"errors": {
|
||||
"not_found": "Not found",
|
||||
"retrieval_intent": "An error occurred while retrieving shared data"
|
||||
}
|
||||
},
|
||||
"startup": {
|
||||
"init": "Initializing...",
|
||||
"start_services": "Starting services..."
|
||||
},
|
||||
"login": {
|
||||
"help": "Login",
|
||||
"compatibility_dialog": {
|
||||
"title": "How to login?",
|
||||
"body": "A FileBin instance >= 3.5.0 and valid credentials for this instance are required."
|
||||
"api": {
|
||||
"forbidden": "You're not allowed to access this resource",
|
||||
"bad_request": "Bad request: {reason}",
|
||||
"socket_error": "Not a valid host or no internet connection",
|
||||
"socket_timeout": "Request timed out - please ensure your internet connection is stable and that you're connecting to a valid FileBin instance and that it's not under heavy load",
|
||||
"general_rest_error": "An unknown error occurred during communication",
|
||||
"general_rest_error_payload": "An unknown error occurred during communication: {message}"
|
||||
},
|
||||
"url_placeholder": "https://paste.domain.tld",
|
||||
"username_placeholder": "Username",
|
||||
"password_placeholder": "Password",
|
||||
"button": "Login",
|
||||
"errors": {
|
||||
"empty_url": "Please provide a FileBin URL",
|
||||
"no_protocol": "URLs need to include a valid protocol like http:// or https://",
|
||||
"invalid_url": "Please provide a valid FileBin URL",
|
||||
"empty_username": "Please provide a username",
|
||||
"empty_password": "Please provide a password",
|
||||
"wrong_credentials": "Credentials are invalid",
|
||||
"forbidden": "You're not allowed to access this instance"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"no_items": "No pastes found",
|
||||
"filename": "Filename",
|
||||
"id": "ID",
|
||||
"filesize": "Filesize",
|
||||
"link": "Link",
|
||||
"date": "Date",
|
||||
"open_link": "Open in browser",
|
||||
"mimetype": "Mimetype",
|
||||
"delete": "Delete permanently",
|
||||
"multipaste_element": "Included as multipaste item",
|
||||
"errors": {
|
||||
"not_found": "No pastes found"
|
||||
"app": {
|
||||
"title": "FileBin Mobile",
|
||||
"unknown_error": "An unknown error occurred, please try again"
|
||||
},
|
||||
"titles": {
|
||||
"login": "Login",
|
||||
"history": "History",
|
||||
"profile": "Profile",
|
||||
"about": "About",
|
||||
"upload": "Upload"
|
||||
},
|
||||
"upload": {
|
||||
"and_or": "and/or",
|
||||
"open_file_explorer": "Select file(s)...",
|
||||
"clear_temporary_files": "Clear",
|
||||
"text_to_be_pasted": "Text...",
|
||||
"upload": "Upload",
|
||||
"uploading_now": "Uploading...",
|
||||
"file_explorer_open": "Selecting files...",
|
||||
"uploaded": "Uploaded! Copied links to clipboard.",
|
||||
"dismiss": "Dismiss",
|
||||
"multipaste": "Combine to a multipaste upload",
|
||||
"errors": {
|
||||
"not_found": "Not found",
|
||||
"retrieval_intent": "An error occurred while retrieving shared data"
|
||||
}
|
||||
},
|
||||
"startup": {
|
||||
"init": "Initializing...",
|
||||
"start_services": "Starting services..."
|
||||
},
|
||||
"login": {
|
||||
"compatibility_dialog": {
|
||||
"title": "How to login?",
|
||||
"body": "A FileBin instance >= 3.5.0 is required. Enter valid user and password or switch to API key login by clicking on the icons right next to this help icon."
|
||||
},
|
||||
"url_placeholder": "https://paste.domain.tld",
|
||||
"apikey_placeholder": "API Key",
|
||||
"username_placeholder": "Username",
|
||||
"password_placeholder": "Password",
|
||||
"button": "Login",
|
||||
"errors": {
|
||||
"empty_url": "Please provide a FileBin URL",
|
||||
"no_protocol": "URLs need to include a valid protocol like http:// or https://",
|
||||
"invalid_url": "Please provide a valid FileBin URL",
|
||||
"empty_username": "Please provide a username",
|
||||
"empty_password": "Please provide a password",
|
||||
"empty_apikey": "Please provide an API key",
|
||||
"wrong_credentials": "Credentials are invalid",
|
||||
"forbidden": "You're not allowed to access this instance",
|
||||
"invalid_api_key": "You're not allowed to use this API key. Please verify that it's valid and at least has access level 'apikey'."
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"no_items": "No pastes found",
|
||||
"filename": "Filename",
|
||||
"id": "ID",
|
||||
"filesize": "Filesize",
|
||||
"link": "Link",
|
||||
"date": "Date",
|
||||
"open_link": "Open in browser",
|
||||
"copy_link": {
|
||||
"description": "Copy link",
|
||||
"dismiss": "Dismiss",
|
||||
"copied": "Copied link to clipboard."
|
||||
},
|
||||
"mimetype": "Mimetype",
|
||||
"delete": "Delete permanently",
|
||||
"multipaste_element": "Included as multipaste item",
|
||||
"errors": {
|
||||
"not_found": "No pastes found"
|
||||
},
|
||||
"delete_dialog": {
|
||||
"title": "Are you sure?",
|
||||
"description": "Paste '{id}' will be deleted permanently.",
|
||||
"accept": "Yes",
|
||||
"deny": "Rather not"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"versions": "{appName} ({packageName}) {version}+{buildNumber}",
|
||||
"description": "This application is a mobile client for FileBin and it's open source. It helps you to manage your pastes.\n\nIn order to use the application, you need access to a FileBin instance.",
|
||||
"faq_headline": "F.A.Q",
|
||||
"faq": "- How do I login?\nInsert your instance URL and valid credentials you also use in the web interface of FileBin.\n\n- Why is storage permission required?\nIt's not required, but highly advised to grant it. Otherwise sharing files with the app won't work correctly and you might think that sharing has no effect.\n\n- When I am logged out, sharing files via share with the app won't list all files I selected after I login.\nPlease login before you start using the app. Account information are persisted. You only need to do it once.",
|
||||
"contact_us": "Feedback? Issues?",
|
||||
"website": "https://git.server-speed.net/users/flo/filebin and https://git.myservermanager.com/varakh/fbmobile"
|
||||
},
|
||||
"profile": {
|
||||
"instance": "Instance",
|
||||
"connection": "{url}",
|
||||
"show_config": "Show configuration",
|
||||
"show_config_loading": "Loading configuration...",
|
||||
"shown_config": {
|
||||
"title": "Configuration",
|
||||
"description": "Upload max size: {uploadMaxSize}\n\nMax files per request: {maxFilesPerRequest}\n\nMax inputs vars: {maxInputVars}\n\nRequest max size: {requestMaxSize}",
|
||||
"error": {
|
||||
"title": "Error",
|
||||
"description": "An error occurred while loading the configuration values. Reason: {message}"
|
||||
}
|
||||
},
|
||||
"reveal_api_key": "Reveal API key",
|
||||
"revealed_api_key": {
|
||||
"title": "API key",
|
||||
"description": "{apiKey}"
|
||||
},
|
||||
"logout": "Logout"
|
||||
},
|
||||
"logout": {
|
||||
"title": "Logout",
|
||||
"confirm": "Are you sure?",
|
||||
"yes": "Yes",
|
||||
"no": "No"
|
||||
},
|
||||
"link": {
|
||||
"dialog": {
|
||||
"title": "Link opening failed",
|
||||
"description": "Could not open '{link}'. Please ensure that you have an application installed which handles opening such link types."
|
||||
}
|
||||
},
|
||||
"delete_dialog": {
|
||||
"title": "Are you sure?",
|
||||
"description": "Paste '{id}' will be deleted permanently.",
|
||||
"accept": "Yes",
|
||||
"deny": "Rather not"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"headline": "Welcome to FileBin mobile!",
|
||||
"description": "This application is a mobile client for FileBin and it's open source. It helps you to manage your pastes.\n\nIn order to use the application, you need access to a FileBin instance.",
|
||||
"faq_headline": "F.A.Q",
|
||||
"faq": "- How do I login?\nInsert your instance URL and valid credentials you also use in the web interface of FileBin.\n\n- Why is storage permission required?\nIt's not required, but highly advised to grant it. Otherwise sharing files with the app won't work correctly and you might think that sharing has no effect.\n\n- When I am logged out, sharing files via share with the app won't list all files I selected after I login.\nPlease login before you start using the app. Account information are persisted. You only need to do it once.",
|
||||
"contact_us": "Feedback? Issues?",
|
||||
"website": "Main application: https://github.com/Bluewind/filebin\n\nMobile: https://github.com/v4rakh/fbmobile"
|
||||
},
|
||||
"profile": {
|
||||
"welcome": "Hi!",
|
||||
"connection": "You're currently connected to:\n\nURL: {url}",
|
||||
"config": "Instance configuration:\n\nUpload max size: {uploadMaxSize}\n\nMax files per request: {maxFilesPerRequest}\n\nMax inputs vars: {maxInputVars}\n\nRequest max size: {requestMaxSize}",
|
||||
"reveal_api_key": "Reveal API key",
|
||||
"revealed_api_key": {
|
||||
"title": "API key",
|
||||
"description": "{apiKey}"
|
||||
}
|
||||
},
|
||||
"logout": {
|
||||
"title": "Logout",
|
||||
"confirm": "Are you sure?",
|
||||
"yes": "Yes",
|
||||
"no": "No"
|
||||
},
|
||||
"link": {
|
||||
"dialog": {
|
||||
"title": "Link opening failed",
|
||||
"description": "Could not open '{link}'. Please ensure that you have an application installed which handles opening such link types."
|
||||
"confirm": "OK",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"dev": {
|
||||
"no_route": "No route defined for {route}"
|
||||
}
|
||||
},
|
||||
"permission_service": {
|
||||
"dialog": {
|
||||
"title": "Storage permission",
|
||||
"description": "Storage permission should be granted to the app so that it can work properly. Do you want to grant permission or ignore this message permanently in the future?",
|
||||
"grant": "Grant",
|
||||
"ignore": "Ignore"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"confirm": "OK",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"dev": {
|
||||
"no_route": "No route defined for {route}"
|
||||
}
|
||||
}
|
|
@ -1,227 +1,464 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="128" height="128" id="svg2" version="1.1" inkscape:version="0.48.4 r9939" sodipodi:docname="FileBin.LOGO.6.FINAL.0.svg" inkscape:export-filename="/media/win2/projects/design/FileBin_(paste.xinu.at)_LOGO/FileBin.LOGO.6.FINAL.0.png" inkscape:export-xdpi="300" inkscape:export-ydpi="300">
|
||||
<defs id="defs4">
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881" id="linearGradient13887" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13831-5" id="linearGradient13837-1" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-5">
|
||||
<stop style="stop-color:#ef2929;stop-opacity:1" offset="0" id="stop13833-4"/>
|
||||
<stop style="stop-color:#a40000;stop-opacity:1" offset="1" id="stop13835-1"/>
|
||||
</linearGradient>
|
||||
<linearGradient gradientTransform="translate(-0.7412829,-138.61258)" inkscape:collect="always" xlink:href="#linearGradient13831-4" id="linearGradient13837-6" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4">
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="0" id="stop13833-0"/>
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1" offset="1" id="stop13835-4"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5" id="linearGradient13887-0" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5" id="linearGradient13991" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5" id="linearGradient13999" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5" id="linearGradient14007" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5" id="linearGradient14015" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5" id="linearGradient14023" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14045" xlink:href="#linearGradient13881-5" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14047" xlink:href="#linearGradient13881-5" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14049" xlink:href="#linearGradient13881-5" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14051" xlink:href="#linearGradient13881-5" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14053" xlink:href="#linearGradient13881-5" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2" id="linearGradient13887-0-0" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5"/>
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14053-2" xlink:href="#linearGradient13881-5-2" inkscape:collect="always"/>
|
||||
<linearGradient gradientTransform="translate(502.01164,0.78745356)" inkscape:collect="always" xlink:href="#linearGradient13831-4-2-6" id="linearGradient13837-6-9-0" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4-2-6">
|
||||
<stop style="stop-color:#babdb6;stop-opacity:1" offset="0" id="stop13833-0-6-8"/>
|
||||
<stop style="stop-color:#555753;stop-opacity:1" offset="1" id="stop13835-4-6-2"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2" id="linearGradient13887-0-0-5" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2-2">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8-0"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5-1"/>
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14053-2-2" xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14045-3-2" xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14047-4-9" xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14049-8-7" xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14051-6-4" xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2-2" id="linearGradient13887-0-0-5-9" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2-2-2">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8-0-3"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5-1-1"/>
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14053-2-2-2" xlink:href="#linearGradient13881-5-2-2-2" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2" id="linearGradient14674" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2" id="linearGradient14676" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881" id="linearGradient14704" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881" id="linearGradient14706" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5" id="linearGradient14724" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5" id="linearGradient14726" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2" id="linearGradient14728" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2" id="linearGradient14730" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient gradientTransform="translate(502.01164,0.78745356)" inkscape:collect="always" xlink:href="#linearGradient13831-4-2-6-5" id="linearGradient13837-6-9-0-0" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4-2-6-5">
|
||||
<stop style="stop-color:#babdb6;stop-opacity:1" offset="0" id="stop13833-0-6-8-6"/>
|
||||
<stop style="stop-color:#555753;stop-opacity:1" offset="1" id="stop13835-4-6-2-5"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2-1" id="linearGradient14730-6" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2-2-1">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8-0-0"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5-1-7"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2-1" id="linearGradient14728-5" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14045-3-2-7" xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14047-4-9-3" xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14049-8-7-2" xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14051-6-4-5" xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14800" xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-4" id="linearGradient14676-9" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2-4">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8-00"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5-7"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-4" id="linearGradient14674-9" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14963" xlink:href="#linearGradient13881-5-2-4" inkscape:collect="always"/>
|
||||
<linearGradient gradientTransform="translate(-0.7412829,-138.61258)" inkscape:collect="always" xlink:href="#linearGradient13831-4-3" id="linearGradient13837-6-1" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4-3">
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="0" id="stop13833-0-3"/>
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1" offset="1" id="stop13835-4-0"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-27" id="linearGradient14726-7" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-27">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-9"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-54"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-27" id="linearGradient14724-8" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14045-1" xlink:href="#linearGradient13881-5-27" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14047-0" xlink:href="#linearGradient13881-5-27" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14049-3" xlink:href="#linearGradient13881-5-27" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14051-1" xlink:href="#linearGradient13881-5-27" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient15126" xlink:href="#linearGradient13881-5-27" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-9" id="linearGradient14706-8" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-9">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-9"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-7"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-9" id="linearGradient14704-4" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient15289" xlink:href="#linearGradient13881-9" inkscape:collect="always"/>
|
||||
<linearGradient id="linearGradient13831-4-27">
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="0" id="stop13833-0-9"/>
|
||||
<stop style="stop-color:#204a87;stop-opacity:1;" offset="1" id="stop13835-4-2"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient14726-0" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-3">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-4"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-6"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient14724-3" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14110" xlink:href="#linearGradient13881-5-3" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13831-4-27" id="linearGradient15013" gradientUnits="userSpaceOnUse" gradientTransform="translate(173.56054,-271.90218)" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient15015" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient15017" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient15019" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient15021" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient15023" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient15025" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient gradientTransform="translate(-0.7412829,-138.61258)" inkscape:collect="always" xlink:href="#linearGradient13831-4-3-8" id="linearGradient13837-6-1-2" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4-3-8">
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="0" id="stop13833-0-3-8"/>
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1" offset="1" id="stop13835-4-0-4"/>
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient15126-7" xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-27-8">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-9-7"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-54-4"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-27-8" id="linearGradient14724-8-3" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14045-1-0" xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14047-0-5" xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14049-3-2" xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient14051-1-3" xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient15095" xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always"/>
|
||||
<linearGradient id="linearGradient13831-4-27-2">
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1;" offset="0" id="stop13833-0-9-6"/>
|
||||
<stop style="stop-color:#204a87;stop-opacity:1;" offset="1" id="stop13835-4-2-1"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6" id="linearGradient15025-3" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-3-6">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-4-6"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-6-0"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6" id="linearGradient15023-5" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient15260" xlink:href="#linearGradient13881-5-3-6" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13831-4-27-2-5" id="linearGradient15370-4" gradientUnits="userSpaceOnUse" gradientTransform="translate(350.34367,-250.09554)" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642"/>
|
||||
<linearGradient id="linearGradient13831-4-27-2-5">
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1;" offset="0" id="stop13833-0-9-6-7"/>
|
||||
<stop style="stop-color:#204a87;stop-opacity:1;" offset="1" id="stop13835-4-2-1-3"/>
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient15260-9" xlink:href="#linearGradient13881-5-3-6-4" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-3-6-4">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-4-6-5"/>
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-6-0-2"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4" id="linearGradient15023-5-0" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4" id="linearGradient15015-5-1" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4" id="linearGradient15017-6-4" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4" id="linearGradient15019-0-7" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4" id="linearGradient15021-5-6" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587" gradientUnits="userSpaceOnUse" id="linearGradient15457" xlink:href="#linearGradient13881-5-3-6-4" inkscape:collect="always"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13831-4-27-2" id="linearGradient15552" gradientUnits="userSpaceOnUse" gradientTransform="translate(350.34367,-250.09554)" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6" id="linearGradient15554" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6" id="linearGradient15556" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6" id="linearGradient15558" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6" id="linearGradient15560" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6" id="linearGradient15562" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6" id="linearGradient15564" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient3360" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3" id="linearGradient3362" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"/>
|
||||
</defs>
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1.4142136" inkscape:cx="116.01422" inkscape:cy="115.13684" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" showguides="true" inkscape:guide-bbox="true" inkscape:window-width="1231" inkscape:window-height="1138" inkscape:window-x="1920" inkscape:window-y="0" inkscape:window-maximized="0" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0">
|
||||
<sodipodi:guide orientation="1,0" position="-178.89701,175.46456" id="guide13939"/>
|
||||
<sodipodi:guide orientation="0,1" position="3.7515623,-42.079504" id="guide15364"/>
|
||||
<sodipodi:guide orientation="1,0" position="0.0010123365,156.27187" id="guide13111"/>
|
||||
<sodipodi:guide orientation="0,1" position="-50.910678,0.80919395" id="guide14630"/>
|
||||
<sodipodi:guide orientation="0,1" position="450.77709,156.27187" id="guide15384"/>
|
||||
<sodipodi:guide orientation="1,0" position="177.13125,183.59629" id="guide15386"/>
|
||||
<sodipodi:guide orientation="1,0" position="351.07952,174.4039" id="guide15388"/>
|
||||
</sodipodi:namedview>
|
||||
<metadata id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-231.22291,-886.63406)">
|
||||
<g id="g14997" transform="matrix(0.99999368,0,0,0.99285775,-0.34584976,277.87803)">
|
||||
<rect ry="13.27135" y="615.24164" x="231.57103" height="126.81434" width="128" id="rect12980-4-1-8-0-8" style="color:#000000;fill:#183866;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
<rect ry="13.395431" y="613.13519" x="231.57022" height="128" width="128" id="rect12980-4-1-3-02" style="color:#000000;fill:url(#linearGradient15013);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
<g style="opacity:0.3;fill:#000000" transform="matrix(0.25,0,0,0.25,202.56698,546.71978)" id="g14368-1-1-4-9-6">
|
||||
<g style="fill:#000000" id="g14380-1-5-9-4-9">
|
||||
<rect transform="matrix(1,0,-0.44619856,0.89493399,0,0)" ry="0" y="613.62866" x="498.49884" height="58.009731" width="246.91225" id="rect12978-93-3-97-3-4-8" style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
<rect transform="matrix(1,0,-0.45382734,0.89108964,0,0)" ry="0" y="726.05896" x="554.76031" height="58.259998" width="320.02499" id="rect12978-9-6-44-8-0-2-6" style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
<rect transform="matrix(1,0,-0.44127854,0.89737018,0,0)" ry="0" y="502.93268" x="446.27722" height="57.852242" width="295.71799" id="rect12978-5-3-33-8-6-4-7" style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
<rect ry="0" rx="0" transform="matrix(1,0,-0.44219356,0.89691965,0,0)" y="394.08627" x="398.67178" height="57.881306" width="192.59586" id="rect12978-5-4-7-8-3-8-7-1" style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="128" height="128" id="svg2"
|
||||
version="1.1" inkscape:version="0.48.4 r9939" sodipodi:docname="FileBin.LOGO.6.FINAL.0.svg"
|
||||
inkscape:export-filename="/media/win2/projects/design/FileBin_(paste.xinu.at)_LOGO/FileBin.LOGO.6.FINAL.0.png"
|
||||
inkscape:export-xdpi="300" inkscape:export-ydpi="300">
|
||||
<defs id="defs4">
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881"
|
||||
id="linearGradient13887" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13831-5"
|
||||
id="linearGradient13837-1" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-5">
|
||||
<stop style="stop-color:#ef2929;stop-opacity:1" offset="0" id="stop13833-4" />
|
||||
<stop style="stop-color:#a40000;stop-opacity:1" offset="1" id="stop13835-1" />
|
||||
</linearGradient>
|
||||
<linearGradient gradientTransform="translate(-0.7412829,-138.61258)"
|
||||
inkscape:collect="always" xlink:href="#linearGradient13831-4" id="linearGradient13837-6"
|
||||
x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4">
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="0" id="stop13833-0" />
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1" offset="1" id="stop13835-4" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5"
|
||||
id="linearGradient13887-0" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5"
|
||||
id="linearGradient13991" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5"
|
||||
id="linearGradient13999" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5"
|
||||
id="linearGradient14007" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5"
|
||||
id="linearGradient14015" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5"
|
||||
id="linearGradient14023" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14045"
|
||||
xlink:href="#linearGradient13881-5" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14047"
|
||||
xlink:href="#linearGradient13881-5" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14049"
|
||||
xlink:href="#linearGradient13881-5" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14051"
|
||||
xlink:href="#linearGradient13881-5" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14053"
|
||||
xlink:href="#linearGradient13881-5" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2"
|
||||
id="linearGradient13887-0-0" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5" />
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14053-2"
|
||||
xlink:href="#linearGradient13881-5-2" inkscape:collect="always" />
|
||||
<linearGradient gradientTransform="translate(502.01164,0.78745356)"
|
||||
inkscape:collect="always" xlink:href="#linearGradient13831-4-2-6"
|
||||
id="linearGradient13837-6-9-0" x1="128.57443" y1="886.22906" x2="128.57443"
|
||||
y2="1012.7642" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4-2-6">
|
||||
<stop style="stop-color:#babdb6;stop-opacity:1" offset="0" id="stop13833-0-6-8" />
|
||||
<stop style="stop-color:#555753;stop-opacity:1" offset="1" id="stop13835-4-6-2" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2"
|
||||
id="linearGradient13887-0-0-5" x1="363.7587" y1="781.0882" x2="363.7587" y2="270.32452"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2-2">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8-0" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5-1" />
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14053-2-2"
|
||||
xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14045-3-2"
|
||||
xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14047-4-9"
|
||||
xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14049-8-7"
|
||||
xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14051-6-4"
|
||||
xlink:href="#linearGradient13881-5-2-2" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2-2"
|
||||
id="linearGradient13887-0-0-5-9" x1="363.7587" y1="781.0882" x2="363.7587"
|
||||
y2="270.32452" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2-2-2">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8-0-3" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5-1-1" />
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14053-2-2-2"
|
||||
xlink:href="#linearGradient13881-5-2-2-2" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2"
|
||||
id="linearGradient14674" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2"
|
||||
id="linearGradient14676" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881"
|
||||
id="linearGradient14704" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881"
|
||||
id="linearGradient14706" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5"
|
||||
id="linearGradient14724" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5"
|
||||
id="linearGradient14726" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2"
|
||||
id="linearGradient14728" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2"
|
||||
id="linearGradient14730" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient gradientTransform="translate(502.01164,0.78745356)"
|
||||
inkscape:collect="always" xlink:href="#linearGradient13831-4-2-6-5"
|
||||
id="linearGradient13837-6-9-0-0" x1="128.57443" y1="886.22906" x2="128.57443"
|
||||
y2="1012.7642" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4-2-6-5">
|
||||
<stop style="stop-color:#babdb6;stop-opacity:1" offset="0" id="stop13833-0-6-8-6" />
|
||||
<stop style="stop-color:#555753;stop-opacity:1" offset="1" id="stop13835-4-6-2-5" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2-1"
|
||||
id="linearGradient14730-6" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2-2-1">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8-0-0" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5-1-7" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-2-1"
|
||||
id="linearGradient14728-5" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14045-3-2-7"
|
||||
xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14047-4-9-3"
|
||||
xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14049-8-7-2"
|
||||
xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14051-6-4-5"
|
||||
xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14800"
|
||||
xlink:href="#linearGradient13881-5-2-2-1" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-4"
|
||||
id="linearGradient14676-9" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-2-4">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-8-00" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-5-7" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-2-4"
|
||||
id="linearGradient14674-9" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14963"
|
||||
xlink:href="#linearGradient13881-5-2-4" inkscape:collect="always" />
|
||||
<linearGradient gradientTransform="translate(-0.7412829,-138.61258)"
|
||||
inkscape:collect="always" xlink:href="#linearGradient13831-4-3"
|
||||
id="linearGradient13837-6-1" x1="128.57443" y1="886.22906" x2="128.57443" y2="1012.7642"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4-3">
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="0" id="stop13833-0-3" />
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1" offset="1" id="stop13835-4-0" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-27"
|
||||
id="linearGradient14726-7" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-27">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-9" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-54" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-27"
|
||||
id="linearGradient14724-8" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14045-1"
|
||||
xlink:href="#linearGradient13881-5-27" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14047-0"
|
||||
xlink:href="#linearGradient13881-5-27" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14049-3"
|
||||
xlink:href="#linearGradient13881-5-27" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14051-1"
|
||||
xlink:href="#linearGradient13881-5-27" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient15126"
|
||||
xlink:href="#linearGradient13881-5-27" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-9"
|
||||
id="linearGradient14706-8" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-9">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-9" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-7" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-9"
|
||||
id="linearGradient14704-4" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient15289"
|
||||
xlink:href="#linearGradient13881-9" inkscape:collect="always" />
|
||||
<linearGradient id="linearGradient13831-4-27">
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="0" id="stop13833-0-9" />
|
||||
<stop style="stop-color:#204a87;stop-opacity:1;" offset="1" id="stop13835-4-2" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient14726-0" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-3">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-4" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-6" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient14724-3" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14110"
|
||||
xlink:href="#linearGradient13881-5-3" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13831-4-27"
|
||||
id="linearGradient15013" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(173.56054,-271.90218)" x1="128.57443" y1="886.22906"
|
||||
x2="128.57443" y2="1012.7642" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient15015" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient15017" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient15019" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient15021" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient15023" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient15025" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient gradientTransform="translate(-0.7412829,-138.61258)"
|
||||
inkscape:collect="always" xlink:href="#linearGradient13831-4-3-8"
|
||||
id="linearGradient13837-6-1-2" x1="128.57443" y1="886.22906" x2="128.57443"
|
||||
y2="1012.7642" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13831-4-3-8">
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="0" id="stop13833-0-3-8" />
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1" offset="1" id="stop13835-4-0-4" />
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient15126-7"
|
||||
xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-27-8">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-9-7" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-54-4" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-27-8"
|
||||
id="linearGradient14724-8-3" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14045-1-0"
|
||||
xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14047-0-5"
|
||||
xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14049-3-2"
|
||||
xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient14051-1-3"
|
||||
xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient15095"
|
||||
xlink:href="#linearGradient13881-5-27-8" inkscape:collect="always" />
|
||||
<linearGradient id="linearGradient13831-4-27-2">
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1;" offset="0" id="stop13833-0-9-6" />
|
||||
<stop style="stop-color:#204a87;stop-opacity:1;" offset="1" id="stop13835-4-2-1" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6"
|
||||
id="linearGradient15025-3" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-3-6">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-4-6" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-6-0" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6"
|
||||
id="linearGradient15023-5" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient15260"
|
||||
xlink:href="#linearGradient13881-5-3-6" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13831-4-27-2-5"
|
||||
id="linearGradient15370-4" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(350.34367,-250.09554)" x1="128.57443" y1="886.22906"
|
||||
x2="128.57443" y2="1012.7642" />
|
||||
<linearGradient id="linearGradient13831-4-27-2-5">
|
||||
<stop style="stop-color:#3465a4;stop-opacity:1;" offset="0" id="stop13833-0-9-6-7" />
|
||||
<stop style="stop-color:#204a87;stop-opacity:1;" offset="1" id="stop13835-4-2-1-3" />
|
||||
</linearGradient>
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient15260-9"
|
||||
xlink:href="#linearGradient13881-5-3-6-4" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" id="linearGradient13881-5-3-6-4">
|
||||
<stop style="stop-color:#eeeeec;stop-opacity:1" offset="0" id="stop13883-7-4-6-5" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop13885-3-6-0-2" />
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4"
|
||||
id="linearGradient15023-5-0" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4"
|
||||
id="linearGradient15015-5-1" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4"
|
||||
id="linearGradient15017-6-4" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4"
|
||||
id="linearGradient15019-0-7" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6-4"
|
||||
id="linearGradient15021-5-6" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient y2="270.32452" x2="363.7587" y1="781.0882" x1="363.7587"
|
||||
gradientUnits="userSpaceOnUse" id="linearGradient15457"
|
||||
xlink:href="#linearGradient13881-5-3-6-4" inkscape:collect="always" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13831-4-27-2"
|
||||
id="linearGradient15552" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(350.34367,-250.09554)" x1="128.57443" y1="886.22906"
|
||||
x2="128.57443" y2="1012.7642" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6"
|
||||
id="linearGradient15554" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6"
|
||||
id="linearGradient15556" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6"
|
||||
id="linearGradient15558" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6"
|
||||
id="linearGradient15560" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6"
|
||||
id="linearGradient15562" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3-6"
|
||||
id="linearGradient15564" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient3360" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient13881-5-3"
|
||||
id="linearGradient3362" gradientUnits="userSpaceOnUse" x1="363.7587" y1="781.0882"
|
||||
x2="363.7587" y2="270.32452" />
|
||||
</defs>
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1.4142136"
|
||||
inkscape:cx="116.01422" inkscape:cy="115.13684" inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1" showgrid="false" showguides="true"
|
||||
inkscape:guide-bbox="true" inkscape:window-width="1231" inkscape:window-height="1138"
|
||||
inkscape:window-x="1920" inkscape:window-y="0" inkscape:window-maximized="0"
|
||||
fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0">
|
||||
<sodipodi:guide orientation="1,0" position="-178.89701,175.46456" id="guide13939" />
|
||||
<sodipodi:guide orientation="0,1" position="3.7515623,-42.079504" id="guide15364" />
|
||||
<sodipodi:guide orientation="1,0" position="0.0010123365,156.27187" id="guide13111" />
|
||||
<sodipodi:guide orientation="0,1" position="-50.910678,0.80919395" id="guide14630" />
|
||||
<sodipodi:guide orientation="0,1" position="450.77709,156.27187" id="guide15384" />
|
||||
<sodipodi:guide orientation="1,0" position="177.13125,183.59629" id="guide15386" />
|
||||
<sodipodi:guide orientation="1,0" position="351.07952,174.4039" id="guide15388" />
|
||||
</sodipodi:namedview>
|
||||
<metadata id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1"
|
||||
transform="translate(-231.22291,-886.63406)">
|
||||
<g id="g14997" transform="matrix(0.99999368,0,0,0.99285775,-0.34584976,277.87803)">
|
||||
<rect ry="13.27135" y="615.24164" x="231.57103" height="126.81434" width="128"
|
||||
id="rect12980-4-1-8-0-8"
|
||||
style="color:#000000;fill:#183866;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect ry="13.395431" y="613.13519" x="231.57022" height="128" width="128"
|
||||
id="rect12980-4-1-3-02"
|
||||
style="color:#000000;fill:url(#linearGradient15013);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<g style="opacity:0.3;fill:#000000"
|
||||
transform="matrix(0.25,0,0,0.25,202.56698,546.71978)" id="g14368-1-1-4-9-6">
|
||||
<g style="fill:#000000" id="g14380-1-5-9-4-9">
|
||||
<rect transform="matrix(1,0,-0.44619856,0.89493399,0,0)" ry="0" y="613.62866"
|
||||
x="498.49884" height="58.009731" width="246.91225"
|
||||
id="rect12978-93-3-97-3-4-8"
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect transform="matrix(1,0,-0.45382734,0.89108964,0,0)" ry="0" y="726.05896"
|
||||
x="554.76031" height="58.259998" width="320.02499"
|
||||
id="rect12978-9-6-44-8-0-2-6"
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect transform="matrix(1,0,-0.44127854,0.89737018,0,0)" ry="0" y="502.93268"
|
||||
x="446.27722" height="57.852242" width="295.71799"
|
||||
id="rect12978-5-3-33-8-6-4-7"
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect ry="0" rx="0" transform="matrix(1,0,-0.44219356,0.89691965,0,0)"
|
||||
y="394.08627" x="398.67178" height="57.881306" width="192.59586"
|
||||
id="rect12978-5-4-7-8-3-8-7-1"
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
<g style="fill:url(#linearGradient3362);fill-opacity:1"
|
||||
transform="matrix(0.25,0,0,0.25,202.55842,545.58997)" id="g14368-1-1-71-1">
|
||||
<g style="fill:url(#linearGradient3360);fill-opacity:1" id="g14380-1-5-08-8">
|
||||
<rect transform="matrix(1,0,-0.44619856,0.89493399,0,0)" ry="0" y="613.62866"
|
||||
x="498.49884" height="58.009731" width="246.91225"
|
||||
id="rect12978-93-3-97-37-0"
|
||||
style="color:#000000;fill:url(#linearGradient15015);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect transform="matrix(1,0,-0.45382734,0.89108964,0,0)" ry="0" y="726.05896"
|
||||
x="554.76031" height="58.259998" width="320.02499"
|
||||
id="rect12978-9-6-44-8-46-5"
|
||||
style="color:#000000;fill:url(#linearGradient15017);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect transform="matrix(1,0,-0.44127854,0.89737018,0,0)" ry="0" y="502.93268"
|
||||
x="446.27722" height="57.852242" width="295.71799"
|
||||
id="rect12978-5-3-33-8-33-29"
|
||||
style="color:#000000;fill:url(#linearGradient15019);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect ry="0" rx="0" transform="matrix(1,0,-0.44219356,0.89691965,0,0)"
|
||||
y="394.08627" x="398.67178" height="57.881306" width="192.59586"
|
||||
id="rect12978-5-4-7-8-3-9-6"
|
||||
style="color:#000000;fill:url(#linearGradient15021);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g style="fill:url(#linearGradient3362);fill-opacity:1" transform="matrix(0.25,0,0,0.25,202.55842,545.58997)" id="g14368-1-1-71-1">
|
||||
<g style="fill:url(#linearGradient3360);fill-opacity:1" id="g14380-1-5-08-8">
|
||||
<rect transform="matrix(1,0,-0.44619856,0.89493399,0,0)" ry="0" y="613.62866" x="498.49884" height="58.009731" width="246.91225" id="rect12978-93-3-97-37-0" style="color:#000000;fill:url(#linearGradient15015);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
<rect transform="matrix(1,0,-0.45382734,0.89108964,0,0)" ry="0" y="726.05896" x="554.76031" height="58.259998" width="320.02499" id="rect12978-9-6-44-8-46-5" style="color:#000000;fill:url(#linearGradient15017);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
<rect transform="matrix(1,0,-0.44127854,0.89737018,0,0)" ry="0" y="502.93268" x="446.27722" height="57.852242" width="295.71799" id="rect12978-5-3-33-8-33-29" style="color:#000000;fill:url(#linearGradient15019);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
<rect ry="0" rx="0" transform="matrix(1,0,-0.44219356,0.89691965,0,0)" y="394.08627" x="398.67178" height="57.881306" width="192.59586" id="rect12978-5-4-7-8-3-9-6" style="color:#000000;fill:url(#linearGradient15021);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:79.67999581;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 36 KiB |
|
@ -1 +1,204 @@
|
|||
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"size": "60x60",
|
||||
"expected-size": "180",
|
||||
"filename": "180.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"size": "40x40",
|
||||
"expected-size": "80",
|
||||
"filename": "80.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "40x40",
|
||||
"expected-size": "120",
|
||||
"filename": "120.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"size": "60x60",
|
||||
"expected-size": "120",
|
||||
"filename": "120.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "57x57",
|
||||
"expected-size": "57",
|
||||
"filename": "57.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"expected-size": "58",
|
||||
"filename": "58.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"expected-size": "29",
|
||||
"filename": "29.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"expected-size": "87",
|
||||
"filename": "87.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"size": "57x57",
|
||||
"expected-size": "114",
|
||||
"filename": "114.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "20x20",
|
||||
"expected-size": "40",
|
||||
"filename": "40.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "20x20",
|
||||
"expected-size": "60",
|
||||
"filename": "60.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"size": "1024x1024",
|
||||
"filename": "1024.png",
|
||||
"expected-size": "1024",
|
||||
"idiom": "ios-marketing",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size": "40x40",
|
||||
"expected-size": "80",
|
||||
"filename": "80.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "72x72",
|
||||
"expected-size": "72",
|
||||
"filename": "72.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size": "76x76",
|
||||
"expected-size": "152",
|
||||
"filename": "152.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "50x50",
|
||||
"expected-size": "100",
|
||||
"filename": "100.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"expected-size": "58",
|
||||
"filename": "58.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "76x76",
|
||||
"expected-size": "76",
|
||||
"filename": "76.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"expected-size": "29",
|
||||
"filename": "29.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size": "50x50",
|
||||
"expected-size": "50",
|
||||
"filename": "50.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size": "72x72",
|
||||
"expected-size": "144",
|
||||
"filename": "144.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "40x40",
|
||||
"expected-size": "40",
|
||||
"filename": "40.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size": "83.5x83.5",
|
||||
"expected-size": "167",
|
||||
"filename": "167.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "20x20",
|
||||
"expected-size": "20",
|
||||
"filename": "20.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size": "20x20",
|
||||
"expected-size": "40",
|
||||
"filename": "40.png",
|
||||
"folder": "Assets.xcassets/AppIcon.appiconset/",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
"images": [
|
||||
{
|
||||
"idiom": "universal",
|
||||
"filename": "LaunchImage.png",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "universal",
|
||||
"filename": "LaunchImage@2x.png",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "universal",
|
||||
"filename": "LaunchImage@3x.png",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,8 +60,13 @@
|
|||
</dict>
|
||||
<dict/>
|
||||
</array>
|
||||
// TODO follow steps 2) on create share extension (https://pub.dev/packages/receive_sharing_intent)
|
||||
// TODO follow steps on create share extension (https://pub.dev/packages/flutter_sharing_intent)
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Allow to select photos and upload them via the app</string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>https</string>
|
||||
<string>http</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
63
lib/app.dart
63
lib/app.dart
|
@ -1,7 +1,7 @@
|
|||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:flutter_translate/localization_provider.dart';
|
||||
import 'package:flutter_translate/localized_app.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'core/enums/refresh_event.dart';
|
||||
|
@ -18,33 +18,54 @@ import 'ui/shared/app_colors.dart';
|
|||
import 'ui/views/startup_view.dart';
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
static final _defaultLightColorScheme = ColorScheme.fromSwatch(
|
||||
primarySwatch: myColor, brightness: Brightness.light);
|
||||
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(
|
||||
primarySwatch: myColor, brightness: Brightness.dark);
|
||||
|
||||
MyApp({super.key}) {
|
||||
initializeDateFormatting('en');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var localizationDelegate = LocalizedApp.of(context).delegate;
|
||||
|
||||
return LocalizationProvider(
|
||||
state: LocalizationProvider.of(context).state,
|
||||
child: StreamProvider<RefreshEvent>(
|
||||
child: StreamProvider<RefreshEvent?>(
|
||||
initialData: null,
|
||||
create: (context) => locator<RefreshService>().refreshHistoryController.stream,
|
||||
child: StreamProvider<Session>(
|
||||
create: (context) =>
|
||||
locator<RefreshService>().refreshEventController.stream,
|
||||
child: StreamProvider<Session?>(
|
||||
initialData: Session.initial(),
|
||||
create: (context) => locator<SessionService>().sessionController.stream,
|
||||
child: LifeCycleManager(
|
||||
child: MaterialApp(
|
||||
title: translate('app.title'),
|
||||
builder: (context, child) => Navigator(
|
||||
key: locator<DialogService>().dialogNavigationKey,
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(builder: (context) => DialogManager(child: child)),
|
||||
),
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light, primarySwatch: primaryAccentColor, primaryColor: primaryAccentColor),
|
||||
onGenerateRoute: AppRouter.generateRoute,
|
||||
navigatorKey: locator<NavigationService>().navigationKey,
|
||||
home: StartUpView(),
|
||||
supportedLocales: localizationDelegate.supportedLocales,
|
||||
locale: localizationDelegate.currentLocale,
|
||||
)),
|
||||
create: (context) =>
|
||||
locator<SessionService>().sessionController.stream,
|
||||
child: LifeCycleManager(child: DynamicColorBuilder(
|
||||
builder: (lightColorScheme, darkColorScheme) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: translate('app.title'),
|
||||
builder: (context, child) => Navigator(
|
||||
key: locator<DialogService>().dialogNavigationKey,
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(
|
||||
builder: (context) => DialogManager(child: child)),
|
||||
),
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme:
|
||||
lightColorScheme ?? _defaultLightColorScheme),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: darkColorScheme ?? _defaultDarkColorScheme),
|
||||
onGenerateRoute: AppRouter.generateRoute,
|
||||
navigatorKey: locator<NavigationService>().navigationKey,
|
||||
home: const StartUpView(),
|
||||
supportedLocales: localizationDelegate.supportedLocales,
|
||||
locale: localizationDelegate.currentLocale,
|
||||
);
|
||||
})),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
class DialogRequest {
|
||||
final String title;
|
||||
final String description;
|
||||
final String buttonTitleAccept;
|
||||
final String buttonTitleDeny;
|
||||
final String? title;
|
||||
final String? description;
|
||||
final String? buttonTitleAccept;
|
||||
final String? buttonTitleDeny;
|
||||
|
||||
DialogRequest({
|
||||
this.title,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class DialogResponse {
|
||||
final bool confirmed;
|
||||
final bool? confirmed;
|
||||
|
||||
DialogResponse({
|
||||
this.confirmed,
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
/// Enums for error codes
|
||||
enum ErrorCode {
|
||||
/// A generic error
|
||||
GENERAL_ERROR,
|
||||
generalError,
|
||||
|
||||
/// Errors related to connections
|
||||
SOCKET_ERROR,
|
||||
SOCKET_TIMEOUT,
|
||||
socketError,
|
||||
socketTimeout,
|
||||
|
||||
/// A REST error (response code wasn't 200 or 204)
|
||||
REST_ERROR,
|
||||
restError,
|
||||
|
||||
/// Custom errors
|
||||
invalidApiKey
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
enum RefreshEvent { RefreshHistory }
|
||||
enum RefreshEvent { refreshHistory }
|
||||
|
|
|
@ -1 +1 @@
|
|||
enum ViewState { Idle, Busy }
|
||||
enum ViewState { idle, busy }
|
||||
|
|
|
@ -5,9 +5,10 @@ class RestServiceException extends ServiceException {
|
|||
final int statusCode;
|
||||
final dynamic responseBody;
|
||||
|
||||
RestServiceException(this.statusCode, {this.responseBody, String message})
|
||||
: super(code: ErrorCode.REST_ERROR, message: message);
|
||||
RestServiceException(this.statusCode, {this.responseBody, super.message = null})
|
||||
: super(code: ErrorCode.restError);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "$code $statusCode $message";
|
||||
}
|
||||
|
|
|
@ -2,10 +2,11 @@ import '../enums/error_code.dart';
|
|||
|
||||
class ServiceException implements Exception {
|
||||
final ErrorCode code;
|
||||
final String message;
|
||||
final String? message;
|
||||
|
||||
ServiceException({this.code = ErrorCode.GENERAL_ERROR, this.message = ''});
|
||||
ServiceException({this.code = ErrorCode.generalError, this.message = ''});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "$code: $message";
|
||||
}
|
||||
|
|
|
@ -6,15 +6,16 @@ import '../datamodels/dialog_response.dart';
|
|||
import '../services/dialog_service.dart';
|
||||
|
||||
class DialogManager extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Widget? child;
|
||||
|
||||
DialogManager({Key key, this.child}) : super(key: key);
|
||||
const DialogManager({super.key, this.child});
|
||||
|
||||
@override
|
||||
_DialogManagerState createState() => _DialogManagerState();
|
||||
}
|
||||
|
||||
class _DialogManagerState extends State<DialogManager> {
|
||||
DialogService _dialogService = locator<DialogService>();
|
||||
final DialogService _dialogService = locator<DialogService>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -24,15 +25,16 @@ class _DialogManagerState extends State<DialogManager> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
return widget.child!;
|
||||
}
|
||||
|
||||
void _showDialog(DialogRequest request) {
|
||||
List<Widget> actions = <Widget>[];
|
||||
|
||||
if (request.buttonTitleDeny != null && request.buttonTitleDeny.isNotEmpty) {
|
||||
Widget denyBtn = FlatButton(
|
||||
child: Text(request.buttonTitleDeny),
|
||||
if (request.buttonTitleDeny != null &&
|
||||
request.buttonTitleDeny!.isNotEmpty) {
|
||||
Widget denyBtn = TextButton(
|
||||
child: Text(request.buttonTitleDeny!),
|
||||
onPressed: () {
|
||||
_dialogService.dialogComplete(DialogResponse(confirmed: false));
|
||||
},
|
||||
|
@ -40,8 +42,8 @@ class _DialogManagerState extends State<DialogManager> {
|
|||
actions.add(denyBtn);
|
||||
}
|
||||
|
||||
Widget confirmBtn = FlatButton(
|
||||
child: Text(request.buttonTitleAccept),
|
||||
Widget confirmBtn = TextButton(
|
||||
child: Text(request.buttonTitleAccept!),
|
||||
onPressed: () {
|
||||
_dialogService.dialogComplete(DialogResponse(confirmed: true));
|
||||
},
|
||||
|
@ -49,8 +51,8 @@ class _DialogManagerState extends State<DialogManager> {
|
|||
actions.add(confirmBtn);
|
||||
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(request.title),
|
||||
content: Text(request.description),
|
||||
title: Text(request.title!),
|
||||
content: Text(request.description!),
|
||||
actions: actions,
|
||||
);
|
||||
|
||||
|
|
|
@ -9,21 +9,26 @@ import '../util/logger.dart';
|
|||
|
||||
/// Stop and start long running services
|
||||
class LifeCycleManager extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Widget? child;
|
||||
|
||||
LifeCycleManager({Key key, this.child}) : super(key: key);
|
||||
const LifeCycleManager({super.key, this.child});
|
||||
|
||||
@override
|
||||
_LifeCycleManagerState createState() => _LifeCycleManagerState();
|
||||
}
|
||||
|
||||
class _LifeCycleManagerState extends State<LifeCycleManager> with WidgetsBindingObserver {
|
||||
class _LifeCycleManagerState extends State<LifeCycleManager>
|
||||
with WidgetsBindingObserver {
|
||||
final Logger logger = getLogger();
|
||||
|
||||
List<StoppableService> servicesToManage = [locator<SessionService>(), locator<PermissionService>()];
|
||||
List<StoppableService> servicesToManage = [
|
||||
locator<SessionService>(),
|
||||
locator<PermissionService>()
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
return widget.child!;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -43,12 +48,12 @@ class _LifeCycleManagerState extends State<LifeCycleManager> with WidgetsBinding
|
|||
logger.d('LifeCycle event ${state.toString()}');
|
||||
super.didChangeAppLifecycleState(state);
|
||||
|
||||
servicesToManage.forEach((service) {
|
||||
for (var service in servicesToManage) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
service.start();
|
||||
} else {
|
||||
service.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
29
lib/core/models/rest/apikey.dart
Normal file
29
lib/core/models/rest/apikey.dart
Normal file
|
@ -0,0 +1,29 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'apikey.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ApiKey {
|
||||
@JsonKey(required: true)
|
||||
final String key;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final String created;
|
||||
|
||||
@JsonKey(required: true, name: 'access_level')
|
||||
final String accessLevel;
|
||||
|
||||
final String? comment;
|
||||
|
||||
ApiKey(
|
||||
{required this.key,
|
||||
required this.created,
|
||||
required this.accessLevel,
|
||||
this.comment});
|
||||
|
||||
// JSON Init
|
||||
factory ApiKey.fromJson(Map<String, dynamic> json) => _$ApiKeyFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$ApiKeyToJson(this);
|
||||
}
|
20
lib/core/models/rest/apikeys.dart
Normal file
20
lib/core/models/rest/apikeys.dart
Normal file
|
@ -0,0 +1,20 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'apikey.dart';
|
||||
|
||||
part 'apikeys.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ApiKeys {
|
||||
@JsonKey(name: "items", required: true)
|
||||
final Map<String, ApiKey> apikeys;
|
||||
|
||||
ApiKeys({required this.apikeys});
|
||||
|
||||
// JSON Init
|
||||
factory ApiKeys.fromJson(Map<String, dynamic> json) =>
|
||||
_$ApiKeysFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$ApiKeysToJson(this);
|
||||
}
|
23
lib/core/models/rest/apikeys_response.dart
Normal file
23
lib/core/models/rest/apikeys_response.dart
Normal file
|
@ -0,0 +1,23 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'apikeys.dart';
|
||||
|
||||
part 'apikeys_response.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ApiKeysResponse {
|
||||
@JsonKey(required: true)
|
||||
final String status;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final ApiKeys data;
|
||||
|
||||
ApiKeysResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory ApiKeysResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$ApiKeysResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$ApiKeysResponseToJson(this);
|
||||
}
|
|
@ -16,7 +16,11 @@ class Config {
|
|||
@JsonKey(name: "request_max_size", required: true)
|
||||
final num requestMaxSize;
|
||||
|
||||
Config({this.uploadMaxSize, this.maxFilesPerRequest, this.maxInputVars, this.requestMaxSize});
|
||||
Config(
|
||||
{required this.uploadMaxSize,
|
||||
required this.maxFilesPerRequest,
|
||||
required this.maxInputVars,
|
||||
required this.requestMaxSize});
|
||||
|
||||
// JSON Init
|
||||
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
|
||||
|
|
|
@ -12,10 +12,11 @@ class ConfigResponse {
|
|||
@JsonKey(required: true)
|
||||
final Config data;
|
||||
|
||||
ConfigResponse({this.status, this.data});
|
||||
ConfigResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory ConfigResponse.fromJson(Map<String, dynamic> json) => _$ConfigResponseFromJson(json);
|
||||
factory ConfigResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$ConfigResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$ConfigResponseToJson(this);
|
||||
|
|
|
@ -10,10 +10,11 @@ class CreateApiKeyResponse {
|
|||
@JsonKey(required: true)
|
||||
final Map<String, String> data;
|
||||
|
||||
CreateApiKeyResponse({this.status, this.data});
|
||||
CreateApiKeyResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory CreateApiKeyResponse.fromJson(Map<String, dynamic> json) => _$CreateApiKeyResponseFromJson(json);
|
||||
factory CreateApiKeyResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$CreateApiKeyResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$CreateApiKeyResponseToJson(this);
|
||||
|
|
|
@ -14,12 +14,13 @@ class History {
|
|||
final Map<String, HistoryMultipasteItem> multipasteItems;
|
||||
|
||||
@JsonKey(name: "total_size")
|
||||
final String totalSize;
|
||||
final String? totalSize;
|
||||
|
||||
History({this.items, this.multipasteItems, this.totalSize});
|
||||
History({required this.items, required this.multipasteItems, this.totalSize});
|
||||
|
||||
// JSON Init
|
||||
factory History.fromJson(Map<String, dynamic> json) => _$HistoryFromJson(json);
|
||||
factory History.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryToJson(this);
|
||||
|
|
|
@ -11,12 +11,20 @@ class HistoryItem {
|
|||
final String filesize;
|
||||
final String hash;
|
||||
final String mimetype;
|
||||
final String thumbnail;
|
||||
final String? thumbnail;
|
||||
|
||||
HistoryItem({this.date, this.filename, this.filesize, this.hash, this.id, this.mimetype, this.thumbnail});
|
||||
HistoryItem(
|
||||
{required this.date,
|
||||
required this.filename,
|
||||
required this.filesize,
|
||||
required this.hash,
|
||||
required this.id,
|
||||
required this.mimetype,
|
||||
this.thumbnail});
|
||||
|
||||
// JSON Init
|
||||
factory HistoryItem.fromJson(Map<String, dynamic> json) => _$HistoryItemFromJson(json);
|
||||
factory HistoryItem.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryItemFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryItemToJson(this);
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'history_item.dart';
|
||||
import 'history_multipaste_item_entry.dart';
|
||||
|
||||
part 'history_multipaste_item.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class HistoryMultipasteItem {
|
||||
final String date;
|
||||
final Map<String, HistoryItem> items;
|
||||
final Map<String, HistoryMultipasteItemEntry> items;
|
||||
|
||||
@JsonKey(name: "url_id")
|
||||
final String urlId;
|
||||
|
||||
HistoryMultipasteItem({this.date, this.items, this.urlId});
|
||||
HistoryMultipasteItem(this.items, {required this.date, required this.urlId});
|
||||
|
||||
// JSON Init
|
||||
factory HistoryMultipasteItem.fromJson(Map<String, dynamic> json) => _$HistoryMultipasteItemFromJson(json);
|
||||
factory HistoryMultipasteItem.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryMultipasteItemFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryMultipasteItemToJson(this);
|
||||
|
|
17
lib/core/models/rest/history_multipaste_item_entry.dart
Normal file
17
lib/core/models/rest/history_multipaste_item_entry.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'history_multipaste_item_entry.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class HistoryMultipasteItemEntry {
|
||||
final String id;
|
||||
|
||||
HistoryMultipasteItemEntry({required this.id});
|
||||
|
||||
// JSON Init
|
||||
factory HistoryMultipasteItemEntry.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryMultipasteItemEntryFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryMultipasteItemEntryToJson(this);
|
||||
}
|
|
@ -12,10 +12,11 @@ class HistoryResponse {
|
|||
@JsonKey(required: true)
|
||||
final History data;
|
||||
|
||||
HistoryResponse({this.status, this.data});
|
||||
HistoryResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory HistoryResponse.fromJson(Map<String, dynamic> json) => _$HistoryResponseFromJson(json);
|
||||
factory HistoryResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryResponseToJson(this);
|
||||
|
|
|
@ -10,12 +10,13 @@ class RestError {
|
|||
final String errorId;
|
||||
|
||||
RestError({
|
||||
this.status,
|
||||
this.message,
|
||||
this.errorId,
|
||||
required this.status,
|
||||
required this.message,
|
||||
required this.errorId,
|
||||
}); // JSON Init
|
||||
|
||||
factory RestError.fromJson(Map<String, dynamic> json) => _$RestErrorFromJson(json);
|
||||
factory RestError.fromJson(Map<String, dynamic> json) =>
|
||||
_$RestErrorFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$RestErrorToJson(this);
|
||||
|
|
|
@ -10,10 +10,11 @@ class Uploaded {
|
|||
@JsonKey(required: true)
|
||||
final List<String> urls;
|
||||
|
||||
Uploaded({this.ids, this.urls});
|
||||
Uploaded({required this.ids, required this.urls});
|
||||
|
||||
// JSON Init
|
||||
factory Uploaded.fromJson(Map<String, dynamic> json) => _$UploadedFromJson(json);
|
||||
factory Uploaded.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedToJson(this);
|
||||
|
|
|
@ -10,10 +10,11 @@ class UploadedMulti {
|
|||
@JsonKey(required: true, name: "url_id")
|
||||
final String urlId;
|
||||
|
||||
UploadedMulti({this.url, this.urlId});
|
||||
UploadedMulti({required this.url, required this.urlId});
|
||||
|
||||
// JSON Init
|
||||
factory UploadedMulti.fromJson(Map<String, dynamic> json) => _$UploadedMultiFromJson(json);
|
||||
factory UploadedMulti.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedMultiFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedMultiToJson(this);
|
||||
|
|
|
@ -12,10 +12,11 @@ class UploadedMultiResponse {
|
|||
@JsonKey(required: true)
|
||||
final UploadedMulti data;
|
||||
|
||||
UploadedMultiResponse({this.status, this.data});
|
||||
UploadedMultiResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory UploadedMultiResponse.fromJson(Map<String, dynamic> json) => _$UploadedMultiResponseFromJson(json);
|
||||
factory UploadedMultiResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedMultiResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedMultiResponseToJson(this);
|
||||
|
|
|
@ -12,10 +12,11 @@ class UploadedResponse {
|
|||
@JsonKey(required: true)
|
||||
final Uploaded data;
|
||||
|
||||
UploadedResponse({this.status, this.data});
|
||||
UploadedResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory UploadedResponse.fromJson(Map<String, dynamic> json) => _$UploadedResponseFromJson(json);
|
||||
factory UploadedResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedResponseToJson(this);
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'rest/config.dart';
|
||||
|
||||
part 'session.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class Session {
|
||||
final String url;
|
||||
final String apiKey;
|
||||
final Config config;
|
||||
|
||||
Session({this.url, this.apiKey, this.config});
|
||||
Session({required this.url, required this.apiKey});
|
||||
|
||||
Session.initial()
|
||||
: url = '',
|
||||
apiKey = '',
|
||||
config = null;
|
||||
apiKey = '';
|
||||
|
||||
factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json);
|
||||
factory Session.fromJson(Map<String, dynamic> json) =>
|
||||
_$SessionFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SessionToJson(this);
|
||||
}
|
||||
|
|
|
@ -4,29 +4,30 @@ part 'uploaded_paste.g.dart';
|
|||
|
||||
@JsonSerializable()
|
||||
class UploadedPaste {
|
||||
final DateTime date;
|
||||
final String filename;
|
||||
final num filesize;
|
||||
final String hash;
|
||||
final DateTime? date;
|
||||
final String? filename;
|
||||
final num? filesize;
|
||||
final String? hash;
|
||||
final String id;
|
||||
final String mimetype;
|
||||
final String thumbnail;
|
||||
final bool isMulti;
|
||||
final List<String> items;
|
||||
final String? mimetype;
|
||||
final String? thumbnail;
|
||||
final bool? isMulti;
|
||||
final List<String?>? items;
|
||||
|
||||
UploadedPaste(
|
||||
{this.date,
|
||||
this.filename,
|
||||
this.filesize,
|
||||
this.hash,
|
||||
this.id,
|
||||
required this.id,
|
||||
this.mimetype,
|
||||
this.thumbnail,
|
||||
this.isMulti,
|
||||
this.items});
|
||||
|
||||
// JSON Init
|
||||
factory UploadedPaste.fromJson(Map<String, dynamic> json) => _$UploadedPasteFromJson(json);
|
||||
factory UploadedPaste.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedPasteFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedPasteToJson(this);
|
||||
|
|
|
@ -11,7 +11,7 @@ import '../models/rest/uploaded_response.dart';
|
|||
import '../services/api.dart';
|
||||
|
||||
class FileRepository {
|
||||
Api _api = locator<Api>();
|
||||
final Api _api = locator<Api>();
|
||||
|
||||
Future<History> getHistory() async {
|
||||
var response = await _api.post('/file/history');
|
||||
|
@ -27,23 +27,29 @@ class FileRepository {
|
|||
return parsedResponse.data;
|
||||
}
|
||||
|
||||
Future delete(String id) async {
|
||||
await _api.post('/file/delete', fields: {'ids[1]': id});
|
||||
Future postDelete(String id) async {
|
||||
var fields = Map.fromEntries([MapEntry("ids[1]", id)]);
|
||||
var response = await _api.post('/file/delete', fields: fields);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<UploadedResponse> upload(List<File> files, Map<String, String> additionalFiles) async {
|
||||
var response = await _api.post('/file/upload', files: files, additionalFiles: additionalFiles);
|
||||
Future<UploadedResponse> postUpload(
|
||||
List<File>? files, Map<String, String>? additionalFiles) async {
|
||||
var response = await _api.post('/file/upload',
|
||||
files: files, additionalFiles: additionalFiles);
|
||||
return UploadedResponse.fromJson(json.decode(response.body));
|
||||
}
|
||||
|
||||
Future createMulti(List<String> ids) async {
|
||||
Map<String, String> multiPasteIds = Map();
|
||||
Future<UploadedMultiResponse> postCreateMultiPaste(List<String> ids) async {
|
||||
Map<String, String> multiPasteIds = {};
|
||||
|
||||
ids.forEach((element) {
|
||||
multiPasteIds.putIfAbsent("ids[${ids.indexOf(element) + 1}]", () => element);
|
||||
});
|
||||
for (var element in ids) {
|
||||
multiPasteIds.putIfAbsent(
|
||||
"ids[${ids.indexOf(element) + 1}]", () => element);
|
||||
}
|
||||
|
||||
var response = await _api.post('/file/create_multipaste', fields: multiPasteIds);
|
||||
var response =
|
||||
await _api.post('/file/create_multipaste', fields: multiPasteIds);
|
||||
return UploadedMultiResponse.fromJson(json.decode(response.body));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import '../../locator.dart';
|
||||
import '../models/rest/apikeys_response.dart';
|
||||
import '../models/rest/create_apikey_response.dart';
|
||||
import '../services/api.dart';
|
||||
|
||||
class UserRepository {
|
||||
Api _api = locator<Api>();
|
||||
final Api _api = locator<Api>();
|
||||
|
||||
Future<CreateApiKeyResponse> createApiKey(
|
||||
String url, String username, String password, String accessLevel, String comment) async {
|
||||
Future<CreateApiKeyResponse> postApiKey(String url, String username,
|
||||
String password, String accessLevel, String comment) async {
|
||||
_api.setUrl(url);
|
||||
|
||||
var response = await _api.post('/user/create_apikey',
|
||||
fields: {'username': username, 'password': password, 'access_level': accessLevel, 'comment': comment});
|
||||
var fields = Map.fromEntries([
|
||||
MapEntry("username", username),
|
||||
MapEntry("password", password),
|
||||
MapEntry("access_level", accessLevel),
|
||||
MapEntry("comment", comment),
|
||||
]);
|
||||
var response = await _api.post('/user/create_apikey', fields: fields);
|
||||
return CreateApiKeyResponse.fromJson(json.decode(response.body));
|
||||
}
|
||||
|
||||
Future<ApiKeysResponse> getApiKeys() async {
|
||||
var response = await _api.post('/user/apikeys');
|
||||
return ApiKeysResponse.fromJson(json.decode(response.body));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,25 +24,34 @@ class Api implements ApiErrorConverter {
|
|||
String _url = "";
|
||||
String _apiKey = "";
|
||||
|
||||
Map<String, String> _headers = {"Content-Type": _applicationJson, "Accept": _applicationJson};
|
||||
Duration _timeout = Duration(seconds: Constants.apiRequestTimeoutLimit);
|
||||
final Map<String, String> _headers = {
|
||||
"Content-Type": _applicationJson,
|
||||
"Accept": _applicationJson
|
||||
};
|
||||
Duration _timeout = const Duration(seconds: Constants.apiRequestTimeoutLimit);
|
||||
|
||||
Future<http.Response> fetch<T>(String route) async {
|
||||
try {
|
||||
_logger
|
||||
.d("Requesting GET API endpoint '${_url + route}' with headers '$_headers' and maximum timeout '$_timeout'");
|
||||
var response = await http.get(_url + route, headers: _headers).timeout(_timeout);
|
||||
_logger.d(
|
||||
"Requesting GET API endpoint '${_url + route}' with headers '$_headers' and maximum timeout '$_timeout'");
|
||||
var response = await http
|
||||
.get(Uri.parse(_url + route), headers: _headers)
|
||||
.timeout(_timeout);
|
||||
handleRestErrors(response);
|
||||
return response;
|
||||
} on TimeoutException {
|
||||
throw ServiceException(code: ErrorCode.SOCKET_TIMEOUT, message: _errorTimeout);
|
||||
throw ServiceException(
|
||||
code: ErrorCode.socketTimeout, message: _errorTimeout);
|
||||
} on SocketException {
|
||||
throw ServiceException(code: ErrorCode.SOCKET_ERROR, message: _errorNoConnection);
|
||||
throw ServiceException(
|
||||
code: ErrorCode.socketError, message: _errorNoConnection);
|
||||
}
|
||||
}
|
||||
|
||||
Future<http.Response> post<T>(String route,
|
||||
{Map<String, String> fields, List<File> files, Map<String, String> additionalFiles}) async {
|
||||
{Map<String, String?>? fields,
|
||||
List<File>? files,
|
||||
Map<String, String>? additionalFiles}) async {
|
||||
try {
|
||||
var uri = Uri.parse(_url + route);
|
||||
var request = http.MultipartRequest('POST', uri)
|
||||
|
@ -54,32 +63,39 @@ class Api implements ApiErrorConverter {
|
|||
}
|
||||
|
||||
if (fields != null && fields.isNotEmpty) {
|
||||
request.fields.addAll(fields);
|
||||
request.fields.addAll(fields as Map<String, String>);
|
||||
}
|
||||
|
||||
if (files != null && files.isNotEmpty) {
|
||||
files.forEach((element) async {
|
||||
request.files.add(await http.MultipartFile.fromPath('file[${files.indexOf(element) + 1}]', element.path));
|
||||
});
|
||||
for (var element in files) {
|
||||
request.files.add(await http.MultipartFile.fromPath(
|
||||
'file[${files.indexOf(element) + 1}]', element.path));
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFiles != null && additionalFiles.length > 0) {
|
||||
if (additionalFiles != null && additionalFiles.isNotEmpty) {
|
||||
List<String> keys = additionalFiles.keys.toList();
|
||||
additionalFiles.forEach((key, value) {
|
||||
var index = files != null ? files.length + keys.indexOf(key) + 1 : keys.indexOf(key) + 1;
|
||||
request.files.add(http.MultipartFile.fromString('file[$index]', value, filename: key));
|
||||
var index = files != null
|
||||
? files.length + keys.indexOf(key) + 1
|
||||
: keys.indexOf(key) + 1;
|
||||
request.files.add(http.MultipartFile.fromString('file[$index]', value,
|
||||
filename: key));
|
||||
});
|
||||
}
|
||||
|
||||
_logger.d("Requesting POST API endpoint '${uri.toString()}' and ${request.files.length} files");
|
||||
_logger.d(
|
||||
"Requesting POST API endpoint '${uri.toString()}' and ${request.files.length} files");
|
||||
var multiResponse = await request.send();
|
||||
var response = await http.Response.fromStream(multiResponse);
|
||||
handleRestErrors(response);
|
||||
return response;
|
||||
} on TimeoutException {
|
||||
throw ServiceException(code: ErrorCode.SOCKET_TIMEOUT, message: _errorTimeout);
|
||||
throw ServiceException(
|
||||
code: ErrorCode.socketTimeout, message: _errorTimeout);
|
||||
} on SocketException {
|
||||
throw ServiceException(code: ErrorCode.SOCKET_ERROR, message: _errorNoConnection);
|
||||
throw ServiceException(
|
||||
code: ErrorCode.socketError, message: _errorNoConnection);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,20 +123,21 @@ class Api implements ApiErrorConverter {
|
|||
/// have a json decoded object. Replace this with a custom
|
||||
/// conversion method by overwriting the interface if needed
|
||||
void handleRestErrors(http.Response response) {
|
||||
if (response != null) {
|
||||
if (response.statusCode != HttpStatus.ok && response.statusCode != HttpStatus.noContent) {
|
||||
if (response.headers.containsKey(HttpHeaders.contentTypeHeader)) {
|
||||
ContentType responseContentType = ContentType.parse(response.headers[HttpHeaders.contentTypeHeader]);
|
||||
if (response.statusCode != HttpStatus.ok &&
|
||||
response.statusCode != HttpStatus.noContent) {
|
||||
if (response.headers.containsKey(HttpHeaders.contentTypeHeader)) {
|
||||
ContentType responseContentType =
|
||||
ContentType.parse(response.headers[HttpHeaders.contentTypeHeader]!);
|
||||
|
||||
if (ContentType.json.primaryType == responseContentType.primaryType &&
|
||||
ContentType.json.subType == responseContentType.subType) {
|
||||
var parsedBody = convert(response);
|
||||
throw new RestServiceException(response.statusCode, responseBody: parsedBody);
|
||||
}
|
||||
if (ContentType.json.primaryType == responseContentType.primaryType &&
|
||||
ContentType.json.subType == responseContentType.subType) {
|
||||
var parsedBody = convert(response);
|
||||
throw RestServiceException(response.statusCode,
|
||||
responseBody: parsedBody);
|
||||
}
|
||||
|
||||
throw new RestServiceException(response.statusCode);
|
||||
}
|
||||
|
||||
throw RestServiceException(response.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,10 @@ import '../datamodels/dialog_request.dart';
|
|||
import '../datamodels/dialog_response.dart';
|
||||
|
||||
class DialogService {
|
||||
GlobalKey<NavigatorState> _dialogNavigationKey = GlobalKey<NavigatorState>();
|
||||
Function(DialogRequest) _showDialogListener;
|
||||
Completer<DialogResponse> _dialogCompleter;
|
||||
final GlobalKey<NavigatorState> _dialogNavigationKey =
|
||||
GlobalKey<NavigatorState>();
|
||||
late Function(DialogRequest) _showDialogListener;
|
||||
Completer<DialogResponse>? _dialogCompleter;
|
||||
|
||||
GlobalKey<NavigatorState> get dialogNavigationKey => _dialogNavigationKey;
|
||||
|
||||
|
@ -18,35 +19,43 @@ class DialogService {
|
|||
}
|
||||
|
||||
Future<DialogResponse> showDialog({
|
||||
String title,
|
||||
String description,
|
||||
String buttonTitleAccept,
|
||||
String? title,
|
||||
String? description,
|
||||
String? buttonTitleAccept,
|
||||
}) {
|
||||
_dialogCompleter = Completer<DialogResponse>();
|
||||
_showDialogListener(DialogRequest(
|
||||
title: title,
|
||||
description: description,
|
||||
buttonTitleAccept:
|
||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept));
|
||||
return _dialogCompleter.future;
|
||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty
|
||||
? translate('dialog.confirm')
|
||||
: buttonTitleAccept));
|
||||
return _dialogCompleter!.future;
|
||||
}
|
||||
|
||||
Future<DialogResponse> showConfirmationDialog(
|
||||
{String title, String description, String buttonTitleAccept, String buttonTitleDeny}) {
|
||||
{String? title,
|
||||
String? description,
|
||||
String? buttonTitleAccept,
|
||||
String? buttonTitleDeny}) {
|
||||
_dialogCompleter = Completer<DialogResponse>();
|
||||
_showDialogListener(DialogRequest(
|
||||
title: title,
|
||||
description: description,
|
||||
buttonTitleAccept:
|
||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept,
|
||||
buttonTitleDeny:
|
||||
buttonTitleDeny == null || buttonTitleDeny.isEmpty ? translate('dialog.cancel') : buttonTitleDeny));
|
||||
return _dialogCompleter.future;
|
||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty
|
||||
? translate('dialog.confirm')
|
||||
: buttonTitleAccept,
|
||||
buttonTitleDeny: buttonTitleDeny == null || buttonTitleDeny.isEmpty
|
||||
? translate('dialog.cancel')
|
||||
: buttonTitleDeny));
|
||||
return _dialogCompleter!.future;
|
||||
}
|
||||
|
||||
void dialogComplete(DialogResponse response) {
|
||||
_dialogNavigationKey.currentState.pop();
|
||||
_dialogCompleter.complete(response);
|
||||
_dialogNavigationKey.currentState!.pop();
|
||||
_dialogCompleter!.complete(response);
|
||||
_dialogCompleter = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,27 +3,32 @@ import 'dart:io';
|
|||
|
||||
import '../../core/repositories/file_repository.dart';
|
||||
import '../../locator.dart';
|
||||
import '../models/rest/config.dart';
|
||||
import '../models/rest/history.dart';
|
||||
import '../models/rest/uploaded_multi_response.dart';
|
||||
import '../models/rest/uploaded_response.dart';
|
||||
|
||||
class FileService {
|
||||
final FileRepository _fileRepository = locator<FileRepository>();
|
||||
|
||||
Future getConfig(String url) async {
|
||||
Future<Config> getConfig(String url) async {
|
||||
return await _fileRepository.getConfig(url);
|
||||
}
|
||||
|
||||
Future getHistory() async {
|
||||
FutureOr<History> getHistory() async {
|
||||
return await _fileRepository.getHistory();
|
||||
}
|
||||
|
||||
Future deletePaste(String id) async {
|
||||
return await _fileRepository.delete(id);
|
||||
return await _fileRepository.postDelete(id);
|
||||
}
|
||||
|
||||
Future upload(List<File> files, Map<String, String> additionalFiles) async {
|
||||
return await _fileRepository.upload(files, additionalFiles);
|
||||
Future<UploadedResponse> uploadPaste(
|
||||
List<File>? files, Map<String, String>? additionalFiles) async {
|
||||
return await _fileRepository.postUpload(files, additionalFiles);
|
||||
}
|
||||
|
||||
Future createMulti(List<String> ids) async {
|
||||
return await _fileRepository.createMulti(ids);
|
||||
Future<UploadedMultiResponse> uploadMultiPaste(List<String> ids) async {
|
||||
return await _fileRepository.postCreateMultiPaste(ids);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:flutter_translate/global.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
|
@ -11,13 +11,16 @@ class LinkService {
|
|||
final DialogService _dialogService = locator<DialogService>();
|
||||
|
||||
Future open(String link) async {
|
||||
if (await canLaunch(link)) {
|
||||
await launch(link);
|
||||
Uri uri = Uri.parse(link);
|
||||
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
_logger.e('Could not launch link $link');
|
||||
_dialogService.showDialog(
|
||||
title: translate('link.dialog.title'),
|
||||
description: translate('link.dialog.description', args: {'link': link}));
|
||||
description:
|
||||
translate('link.dialog.description', args: {'link': link}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:logger/logger.dart';
|
|||
import '../util/logger.dart';
|
||||
|
||||
class NavigationService {
|
||||
GlobalKey<NavigatorState> _navigationKey = GlobalKey<NavigatorState>();
|
||||
final GlobalKey<NavigatorState> _navigationKey = GlobalKey<NavigatorState>();
|
||||
|
||||
GlobalKey<NavigatorState> get navigationKey => _navigationKey;
|
||||
|
||||
|
@ -12,16 +12,18 @@ class NavigationService {
|
|||
|
||||
void pop() {
|
||||
logger.d('NavigationService: pop');
|
||||
_navigationKey.currentState.pop();
|
||||
_navigationKey.currentState!.pop();
|
||||
}
|
||||
|
||||
Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
|
||||
logger.d('NavigationService: navigateTo $routeName');
|
||||
return _navigationKey.currentState.pushNamed(routeName, arguments: arguments);
|
||||
return _navigationKey.currentState!
|
||||
.pushNamed(routeName, arguments: arguments);
|
||||
}
|
||||
|
||||
Future<dynamic> navigateAndReplaceTo(String routeName, {dynamic arguments}) {
|
||||
logger.d('NavigationService: navigateAndReplaceTo $routeName');
|
||||
return _navigationKey.currentState.pushReplacementNamed(routeName, arguments: arguments);
|
||||
return _navigationKey.currentState!
|
||||
.pushReplacementNamed(routeName, arguments: arguments);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,115 +1,109 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
import '../../core/datamodels/dialog_response.dart';
|
||||
import '../../core/services/dialog_service.dart';
|
||||
import '../../core/services/stoppable_service.dart';
|
||||
import '../../core/util/logger.dart';
|
||||
import '../../locator.dart';
|
||||
import 'storage_service.dart';
|
||||
|
||||
class PermissionService extends StoppableService {
|
||||
final Logger _logger = getLogger();
|
||||
final DialogService _dialogService = locator<DialogService>();
|
||||
final StorageService _storageService = locator<StorageService>();
|
||||
|
||||
Timer _serviceCheckTimer;
|
||||
Timer? _serviceCheckTimer;
|
||||
|
||||
PermissionStatus _permissionStatus;
|
||||
|
||||
bool _permanentlyIgnored = false;
|
||||
bool _devicePermissionDialogActive = false;
|
||||
bool _ownPermissionDialogActive = false;
|
||||
|
||||
PermissionService() {
|
||||
_devicePermissionDialogActive = true;
|
||||
bool _deviceInformationInitialized = false;
|
||||
bool _useStoragePermission = true;
|
||||
|
||||
Permission.storage.request().then((status) {
|
||||
_permissionStatus = status;
|
||||
if (PermissionStatus.permanentlyDenied == status) {
|
||||
_permanentlyIgnored = true;
|
||||
}
|
||||
}).whenComplete(() {
|
||||
_logger.d('Initial device request permission finished');
|
||||
_devicePermissionDialogActive = false;
|
||||
});
|
||||
}
|
||||
PermissionService();
|
||||
|
||||
Future checkEnabledAndPermission() async {
|
||||
if (_permanentlyIgnored) {
|
||||
await _storageService.storeStoragePermissionDialogIgnored();
|
||||
_permanentlyIgnored = false;
|
||||
_logger.d('Set permanently ignored permission request');
|
||||
stop();
|
||||
}
|
||||
|
||||
if (_devicePermissionDialogActive) {
|
||||
_logger.d('Device permission dialog active, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ownPermissionDialogActive) {
|
||||
_logger.d('Own permission dialog already active, skipping');
|
||||
return;
|
||||
bool allGranted = false;
|
||||
bool anyPermanentlyDenied = false;
|
||||
|
||||
// Since Android compileSdk >= 33, "storage" is deprecated
|
||||
// Instead, request access to all of
|
||||
// - Permission.photos
|
||||
// - Permission.videos
|
||||
// - Permission.audio
|
||||
//
|
||||
// For iOS and Android < 33, keep using "storage"
|
||||
if (_useStoragePermission) {
|
||||
PermissionStatus storagePermission = await Permission.storage.status;
|
||||
allGranted = PermissionStatus.granted == storagePermission;
|
||||
anyPermanentlyDenied =
|
||||
PermissionStatus.permanentlyDenied == storagePermission;
|
||||
} else {
|
||||
PermissionStatus photosPermission = await Permission.photos.status;
|
||||
PermissionStatus videosPermission = await Permission.videos.status;
|
||||
PermissionStatus audioPermission = await Permission.audio.status;
|
||||
|
||||
allGranted = PermissionStatus.granted == photosPermission &&
|
||||
PermissionStatus.granted == videosPermission &&
|
||||
PermissionStatus.granted == audioPermission;
|
||||
anyPermanentlyDenied =
|
||||
PermissionStatus.permanentlyDenied == photosPermission ||
|
||||
PermissionStatus.permanentlyDenied == videosPermission ||
|
||||
PermissionStatus.permanentlyDenied == audioPermission;
|
||||
}
|
||||
|
||||
var ignoredDialog = await _storageService.hasStoragePermissionDialogIgnored();
|
||||
|
||||
if (ignoredDialog) {
|
||||
_logger.d('Permanently ignored permission request, skipping');
|
||||
// show warning to user to manually handle, don't enforce it over and over again
|
||||
if (anyPermanentlyDenied) {
|
||||
_logger.w(
|
||||
"At least one required permission has been denied permanently, stopping service");
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
_permissionStatus = await Permission.storage.status;
|
||||
if (_permissionStatus != PermissionStatus.granted) {
|
||||
if (_permissionStatus == PermissionStatus.permanentlyDenied) {
|
||||
await _storageService.storeStoragePermissionDialogIgnored();
|
||||
return;
|
||||
}
|
||||
// all good, stop the permission service
|
||||
if (allGranted) {
|
||||
_logger.d("All permissions have been granted, stopping service");
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
_ownPermissionDialogActive = true;
|
||||
DialogResponse response = await _dialogService.showConfirmationDialog(
|
||||
title: translate('permission_service.dialog.title'),
|
||||
description: translate('permission_service.dialog.description'),
|
||||
buttonTitleAccept: translate('permission_service.dialog.grant'),
|
||||
buttonTitleDeny: translate('permission_service.dialog.ignore'));
|
||||
// not all have been granted, show OS dialog
|
||||
_logger.d(
|
||||
"Not all permissions have been granted yet, initializing permission dialog");
|
||||
_devicePermissionDialogActive = true;
|
||||
|
||||
if (!response.confirmed) {
|
||||
await _storageService.storeStoragePermissionDialogIgnored();
|
||||
} else {
|
||||
_devicePermissionDialogActive = true;
|
||||
Permission.storage.request().then((status) async {
|
||||
if (PermissionStatus.permanentlyDenied == status) {
|
||||
await _storageService.storeStoragePermissionDialogIgnored();
|
||||
}
|
||||
}).whenComplete(() {
|
||||
_logger.d('Device request permission finished');
|
||||
_devicePermissionDialogActive = false;
|
||||
});
|
||||
}
|
||||
|
||||
_ownPermissionDialogActive = false;
|
||||
if (_useStoragePermission) {
|
||||
await [Permission.storage].request().whenComplete(() {
|
||||
_logger.d('Device request permission finished');
|
||||
_devicePermissionDialogActive = false;
|
||||
});
|
||||
} else {
|
||||
await _storageService.storeStoragePermissionDialogIgnored();
|
||||
await [Permission.photos, Permission.videos, Permission.audio]
|
||||
.request()
|
||||
.whenComplete(() {
|
||||
_logger.d('Device request permission finished');
|
||||
_devicePermissionDialogActive = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future start() async {
|
||||
super.start();
|
||||
await _determineDeviceInfo();
|
||||
await checkEnabledAndPermission();
|
||||
|
||||
_serviceCheckTimer =
|
||||
Timer.periodic(Duration(milliseconds: Constants.mediaPermissionCheckInterval), (_serviceTimer) async {
|
||||
_serviceCheckTimer = Timer.periodic(
|
||||
const Duration(milliseconds: Constants.mediaPermissionCheckInterval),
|
||||
(serviceTimer) async {
|
||||
if (!super.serviceStopped) {
|
||||
await checkEnabledAndPermission();
|
||||
} else {
|
||||
_serviceTimer.cancel();
|
||||
serviceTimer.cancel();
|
||||
}
|
||||
});
|
||||
_logger.d('PermissionService started');
|
||||
|
@ -122,9 +116,32 @@ class PermissionService extends StoppableService {
|
|||
_logger.d('PermissionService stopped');
|
||||
}
|
||||
|
||||
Future _determineDeviceInfo() async {
|
||||
if (_deviceInformationInitialized) {
|
||||
_logger.d('Device information already initialized, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
if (Platform.isAndroid) {
|
||||
final androidInfo = await deviceInfoPlugin.androidInfo;
|
||||
if (androidInfo.version.sdkInt >= 33) {
|
||||
_useStoragePermission = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_useStoragePermission) {
|
||||
_logger.d('Device requires [storage] permission');
|
||||
} else {
|
||||
_logger.d('Device requires [photos,videos,audio] permission');
|
||||
}
|
||||
|
||||
_deviceInformationInitialized = true;
|
||||
}
|
||||
|
||||
void _removeServiceCheckTimer() {
|
||||
if (_serviceCheckTimer != null) {
|
||||
_serviceCheckTimer.cancel();
|
||||
_serviceCheckTimer!.cancel();
|
||||
_serviceCheckTimer = null;
|
||||
_logger.d('Removed service check timer');
|
||||
}
|
||||
|
|
|
@ -3,11 +3,12 @@ import 'dart:async';
|
|||
import '../enums/refresh_event.dart';
|
||||
|
||||
class RefreshService {
|
||||
StreamController<RefreshEvent> refreshHistoryController = StreamController<RefreshEvent>();
|
||||
StreamController<RefreshEvent> refreshEventController =
|
||||
StreamController<RefreshEvent>.broadcast();
|
||||
|
||||
void addEvent(RefreshEvent event) {
|
||||
if (refreshHistoryController.hasListener) {
|
||||
refreshHistoryController.add(event);
|
||||
if (refreshEventController.hasListener) {
|
||||
refreshEventController.add(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'package:logger/logger.dart';
|
|||
|
||||
import '../../core/services/stoppable_service.dart';
|
||||
import '../../locator.dart';
|
||||
import '../models/rest/config.dart';
|
||||
import '../models/session.dart';
|
||||
import '../services/storage_service.dart';
|
||||
import '../util/logger.dart';
|
||||
|
@ -15,13 +14,24 @@ class SessionService extends StoppableService {
|
|||
final StorageService _storageService = locator<StorageService>();
|
||||
final Api _api = locator<Api>();
|
||||
|
||||
StreamController<Session> sessionController = StreamController<Session>();
|
||||
StreamController<Session?> sessionController = StreamController<Session?>();
|
||||
|
||||
Future<bool> login(String url, String apiKey, Config config) async {
|
||||
void setApiConfig(String url, String? apiKey) {
|
||||
_logger.d('Setting API config for session');
|
||||
_api.setUrl(url);
|
||||
_api.addApiKeyAuthorization(apiKey);
|
||||
}
|
||||
|
||||
var session = new Session(url: url, apiKey: apiKey, config: config);
|
||||
void unsetApiConfig() {
|
||||
_logger.d('Removing API config');
|
||||
_api.removeApiKeyAuthorization();
|
||||
_api.removeUrl();
|
||||
}
|
||||
|
||||
Future<bool> login(String url, String apiKey) async {
|
||||
setApiConfig(url, apiKey);
|
||||
|
||||
var session = Session(url: url, apiKey: apiKey);
|
||||
sessionController.add(session);
|
||||
await _storageService.storeSession(session);
|
||||
_logger.d('Session created');
|
||||
|
@ -29,9 +39,7 @@ class SessionService extends StoppableService {
|
|||
}
|
||||
|
||||
Future<bool> logout() async {
|
||||
_api.removeApiKeyAuthorization();
|
||||
_api.removeUrl();
|
||||
|
||||
unsetApiConfig();
|
||||
sessionController.add(null);
|
||||
_logger.d('Session destroyed');
|
||||
return await _storageService.removeSession();
|
||||
|
|
|
@ -5,45 +5,36 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
import '../models/session.dart';
|
||||
|
||||
class StorageService {
|
||||
static const _SESSION_KEY = 'session';
|
||||
static const _LAST_URL_KEY = 'last_url';
|
||||
static const _STORAGE_PERMISSION_DIALOG_IGNORED = 'storage_permission_ignored';
|
||||
static const _sessionKey = 'session';
|
||||
static const _lastUrlKey = 'last_url';
|
||||
|
||||
Future<bool> storeLastUrl(String url) {
|
||||
return _store(_LAST_URL_KEY, url);
|
||||
return _store(_lastUrlKey, url);
|
||||
}
|
||||
|
||||
Future<String> retrieveLastUrl() async {
|
||||
return await _retrieve(_LAST_URL_KEY);
|
||||
Future<String?> retrieveLastUrl() async {
|
||||
return await _retrieve(_lastUrlKey);
|
||||
}
|
||||
|
||||
Future<bool> hasLastUrl() async {
|
||||
return await _exists(_LAST_URL_KEY);
|
||||
return await _exists(_lastUrlKey);
|
||||
}
|
||||
|
||||
Future<bool> storeSession(Session session) {
|
||||
return _store(_SESSION_KEY, json.encode(session));
|
||||
return _store(_sessionKey, json.encode(session));
|
||||
}
|
||||
|
||||
Future<Session> retrieveSession() async {
|
||||
var retrieve = await _retrieve(_SESSION_KEY);
|
||||
return Session.fromJson(json.decode(retrieve));
|
||||
var retrieve = await _retrieve(_sessionKey);
|
||||
return Session.fromJson(json.decode(retrieve!));
|
||||
}
|
||||
|
||||
Future<bool> hasSession() {
|
||||
return _exists(_SESSION_KEY);
|
||||
return _exists(_sessionKey);
|
||||
}
|
||||
|
||||
Future<bool> removeSession() {
|
||||
return _remove(_SESSION_KEY);
|
||||
}
|
||||
|
||||
Future<bool> storeStoragePermissionDialogIgnored() {
|
||||
return _store(_STORAGE_PERMISSION_DIALOG_IGNORED, true.toString());
|
||||
}
|
||||
|
||||
Future<bool> hasStoragePermissionDialogIgnored() {
|
||||
return _exists(_STORAGE_PERMISSION_DIALOG_IGNORED);
|
||||
return _remove(_sessionKey);
|
||||
}
|
||||
|
||||
Future<bool> _exists(String key) async {
|
||||
|
@ -56,7 +47,7 @@ class StorageService {
|
|||
return prefs.remove(key);
|
||||
}
|
||||
|
||||
Future<String> _retrieve(String key) async {
|
||||
Future<String?> _retrieve(String key) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString(key);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,33 @@
|
|||
import 'dart:async';
|
||||
|
||||
import '../../locator.dart';
|
||||
import '../enums/error_code.dart';
|
||||
import '../error/service_exception.dart';
|
||||
import '../models/rest/apikeys_response.dart';
|
||||
import '../models/rest/create_apikey_response.dart';
|
||||
import '../repositories/user_repository.dart';
|
||||
import 'file_service.dart';
|
||||
|
||||
class UserService {
|
||||
final FileService _fileService = locator<FileService>();
|
||||
final UserRepository _userRepository = locator<UserRepository>();
|
||||
|
||||
Future createApiKey(String url, String username, String password, String accessLevel, String comment) async {
|
||||
return await _userRepository.createApiKey(url, username, password, accessLevel, comment);
|
||||
Future<CreateApiKeyResponse> createApiKey(String url, String username,
|
||||
String password, String accessLevel, String comment) async {
|
||||
return await _userRepository.postApiKey(
|
||||
url, username, password, accessLevel, comment);
|
||||
}
|
||||
|
||||
Future<ApiKeysResponse> getApiKeys() async {
|
||||
return await _userRepository.getApiKeys();
|
||||
}
|
||||
|
||||
/// Use 'getHistory' to check currently used API key to require 'apikey' access level
|
||||
Future<void> checkAccessLevelIsAtLeastApiKey() async {
|
||||
try {
|
||||
await _fileService.getHistory();
|
||||
} on ServiceException catch (e) {
|
||||
throw ServiceException(code: ErrorCode.invalidApiKey, message: e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,14 @@ class FormatterUtil {
|
|||
/// Format epoch timestamp
|
||||
static String formatEpoch(num millis) {
|
||||
DateFormat dateFormat = DateFormat().add_yMEd().add_Hm();
|
||||
return dateFormat.format(DateTime.fromMillisecondsSinceEpoch(millis));
|
||||
return dateFormat
|
||||
.format(DateTime.fromMillisecondsSinceEpoch(millis as int));
|
||||
}
|
||||
|
||||
static String formatBytes(int bytes, int decimals) {
|
||||
if (bytes <= 0) return "0 B";
|
||||
const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
var i = (log(bytes) / log(1024)).floor();
|
||||
return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) + ' ' + suffixes[i];
|
||||
return '${(bytes / pow(1024, i)).toStringAsFixed(decimals)} ${suffixes[i]}';
|
||||
}
|
||||
}
|
||||
|
|
6
lib/core/util/paste_util.dart
Normal file
6
lib/core/util/paste_util.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
class PasteUtil {
|
||||
/// Generate paste link adding a trailing slash
|
||||
static String generateLink(String url, String id) {
|
||||
return '$url/$id/';
|
||||
}
|
||||
}
|
|
@ -1,10 +1,31 @@
|
|||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import '../../core/services/link_service.dart';
|
||||
import '../../locator.dart';
|
||||
import '../enums/viewstate.dart';
|
||||
import 'base_model.dart';
|
||||
|
||||
class AboutModel extends BaseModel {
|
||||
final LinkService _linkService = locator<LinkService>();
|
||||
|
||||
PackageInfo packageInfo = PackageInfo(
|
||||
appName: 'Unknown',
|
||||
packageName: 'Unknown',
|
||||
version: 'Unknown',
|
||||
buildNumber: 'Unknown',
|
||||
);
|
||||
|
||||
void init() async {
|
||||
await _initPackageInfo();
|
||||
}
|
||||
|
||||
Future<void> _initPackageInfo() async {
|
||||
setStateView(ViewState.busy);
|
||||
final PackageInfo info = await PackageInfo.fromPlatform();
|
||||
packageInfo = info;
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
|
||||
void openLink(String link) {
|
||||
_linkService.open(link);
|
||||
}
|
||||
|
|
|
@ -5,35 +5,89 @@ import '../../core/util/logger.dart';
|
|||
import '../enums/viewstate.dart';
|
||||
|
||||
class BaseModel extends ChangeNotifier {
|
||||
static const String stateViewKey = 'viewState';
|
||||
static const String stateMessageKey = 'viewMessage';
|
||||
|
||||
final Logger _logger = getLogger();
|
||||
|
||||
bool _isDisposed = false;
|
||||
|
||||
ViewState _state = ViewState.Idle;
|
||||
String _stateMessage;
|
||||
final Map<String, Object?> _stateMap = {
|
||||
stateViewKey: ViewState.idle,
|
||||
stateMessageKey: null
|
||||
};
|
||||
|
||||
ViewState get state => _state;
|
||||
ViewState? get state => _stateMap[stateViewKey] as ViewState?;
|
||||
|
||||
String get stateMessage => _stateMessage;
|
||||
String? get stateMessage => _stateMap[stateMessageKey] as String?;
|
||||
|
||||
bool getStateValueAsBoolean(String key) {
|
||||
if (_stateMap.containsKey(key) && _stateMap[key] is bool) {
|
||||
return _stateMap[key] as bool;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String? getStateValueAsString(String key) {
|
||||
if (_stateMap.containsKey(key) && _stateMap[key] is String) {
|
||||
return _stateMap[key] as String;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
int? getStateValueAsInt(String key) {
|
||||
if (_stateMap.containsKey(key) && _stateMap[key] is int) {
|
||||
return _stateMap[key] as int;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void setStateBoolValue(String key, bool stateValue) =>
|
||||
_setStateValue(key, stateValue);
|
||||
|
||||
void setStateIntValue(String key, int? stateValue) =>
|
||||
_setStateValue(key, stateValue);
|
||||
|
||||
void setStateStringValue(String key, String? stateValue) =>
|
||||
_setStateValue(key, stateValue);
|
||||
|
||||
void _setStateValue(String key, Object? stateValue) {
|
||||
if (_stateMap.containsKey(key)) {
|
||||
_stateMap.update(key, (value) => stateValue);
|
||||
} else {
|
||||
_stateMap.putIfAbsent(key, () => stateValue);
|
||||
}
|
||||
|
||||
void setState(ViewState viewState) {
|
||||
_state = viewState;
|
||||
if (!_isDisposed) {
|
||||
notifyListeners();
|
||||
_logger.d("Notified state change '${viewState.toString()}'");
|
||||
_logger
|
||||
.d("Notified state value update '($key, ${stateValue.toString()})'");
|
||||
}
|
||||
}
|
||||
|
||||
void setStateMessage(String stateMessage) {
|
||||
_stateMessage = stateMessage;
|
||||
void removeStateValue(String key) {
|
||||
_stateMap.remove(key);
|
||||
|
||||
if (!_isDisposed) {
|
||||
notifyListeners();
|
||||
_logger.d("Notified state message change '$stateMessage'");
|
||||
_logger.d("Notified state removal of '$key'");
|
||||
}
|
||||
}
|
||||
|
||||
void setStateView(ViewState stateView) {
|
||||
_setStateValue(stateViewKey, stateView);
|
||||
}
|
||||
|
||||
void setStateMessage(String? stateMessage) {
|
||||
_setStateValue(stateMessageKey, stateMessage);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_logger.d("Calling dispose");
|
||||
super.dispose();
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
|
|
@ -28,14 +28,15 @@ class HistoryModel extends BaseModel {
|
|||
final LinkService _linkService = locator<LinkService>();
|
||||
final DialogService _dialogService = locator<DialogService>();
|
||||
|
||||
StreamSubscription _refreshTriggerSubscription;
|
||||
late StreamSubscription _refreshTriggerSubscription;
|
||||
|
||||
List<UploadedPaste> pastes = [];
|
||||
String errorMessage;
|
||||
String? errorMessage;
|
||||
|
||||
void init() {
|
||||
this._refreshTriggerSubscription = _refreshService.refreshHistoryController.stream.listen((event) {
|
||||
if (event == RefreshEvent.RefreshHistory) {
|
||||
_refreshTriggerSubscription =
|
||||
_refreshService.refreshEventController.stream.listen((event) {
|
||||
if (event == RefreshEvent.refreshHistory) {
|
||||
_logger.d('History needs a refresh');
|
||||
getHistory();
|
||||
}
|
||||
|
@ -43,18 +44,19 @@ class HistoryModel extends BaseModel {
|
|||
}
|
||||
|
||||
Future getHistory() async {
|
||||
setState(ViewState.Busy);
|
||||
setStateView(ViewState.busy);
|
||||
|
||||
try {
|
||||
pastes.clear();
|
||||
History _history = await _fileService.getHistory();
|
||||
if (_history.items != null) {
|
||||
_history.items.forEach((key, value) {
|
||||
History history = await _fileService.getHistory();
|
||||
if (history.items.isNotEmpty) {
|
||||
history.items.forEach((key, value) {
|
||||
var millisecondsSinceEpoch = int.parse(value.date) * 1000;
|
||||
pastes.add(
|
||||
UploadedPaste(
|
||||
id: key,
|
||||
date: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch),
|
||||
date:
|
||||
DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch),
|
||||
filename: value.filename,
|
||||
filesize: int.parse(value.filesize),
|
||||
hash: value.hash,
|
||||
|
@ -66,8 +68,8 @@ class HistoryModel extends BaseModel {
|
|||
});
|
||||
}
|
||||
|
||||
if (_history.multipasteItems != null) {
|
||||
_history.multipasteItems.forEach((key, multiPaste) {
|
||||
if (history.multipasteItems.isNotEmpty) {
|
||||
history.multipasteItems.forEach((key, multiPaste) {
|
||||
var millisecondsSinceEpoch = int.parse(multiPaste.date) * 1000;
|
||||
pastes.add(UploadedPaste(
|
||||
id: key,
|
||||
|
@ -77,7 +79,7 @@ class HistoryModel extends BaseModel {
|
|||
});
|
||||
}
|
||||
|
||||
pastes.sort((a, b) => a.date.compareTo(b.date));
|
||||
pastes.sort((a, b) => a.date!.compareTo(b.date!));
|
||||
errorMessage = null;
|
||||
} catch (e) {
|
||||
if (e is RestServiceException) {
|
||||
|
@ -90,40 +92,43 @@ class HistoryModel extends BaseModel {
|
|||
e.responseBody is RestError &&
|
||||
e.responseBody.message != null) {
|
||||
if (e.statusCode == HttpStatus.badRequest) {
|
||||
errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message});
|
||||
errorMessage = translate('api.bad_request',
|
||||
args: {'reason': e.responseBody.message});
|
||||
} else {
|
||||
errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message});
|
||||
errorMessage = translate('api.general_rest_error_payload',
|
||||
args: {'message': e.responseBody.message});
|
||||
}
|
||||
} else {
|
||||
errorMessage = translate('api.general_rest_error');
|
||||
}
|
||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||
errorMessage = translate('api.socket_error');
|
||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||
errorMessage = translate('api.socket_timeout');
|
||||
} else {
|
||||
errorMessage = translate('app.unknown_error');
|
||||
setState(ViewState.Idle);
|
||||
_logger.e('An unknown error occurred', e);
|
||||
throw e;
|
||||
setStateView(ViewState.idle);
|
||||
_logger.e('An unknown error occurred', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
|
||||
Future deletePaste(String id) async {
|
||||
DialogResponse res = await _dialogService.showConfirmationDialog(
|
||||
title: translate('history.delete_dialog.title'),
|
||||
description: translate('history.delete_dialog.description', args: {'id': id}),
|
||||
description:
|
||||
translate('history.delete_dialog.description', args: {'id': id}),
|
||||
buttonTitleAccept: translate('history.delete_dialog.accept'),
|
||||
buttonTitleDeny: translate('history.delete_dialog.deny'));
|
||||
|
||||
if (!res.confirmed) {
|
||||
if (!res.confirmed!) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(ViewState.Busy);
|
||||
setStateView(ViewState.busy);
|
||||
|
||||
try {
|
||||
await _fileService.deletePaste(id);
|
||||
|
@ -139,23 +144,24 @@ class HistoryModel extends BaseModel {
|
|||
e.statusCode != HttpStatus.forbidden &&
|
||||
e.responseBody is RestError &&
|
||||
e.responseBody.message != null) {
|
||||
errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message});
|
||||
errorMessage = translate('api.general_rest_error_payload',
|
||||
args: {'message': e.responseBody.message});
|
||||
} else {
|
||||
errorMessage = translate('api.general_rest_error');
|
||||
}
|
||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||
errorMessage = translate('api.socket_error');
|
||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||
errorMessage = translate('api.socket_timeout');
|
||||
} else {
|
||||
errorMessage = translate('app.unknown_error');
|
||||
setState(ViewState.Idle);
|
||||
_logger.e('An unknown error occurred', e);
|
||||
throw e;
|
||||
setStateView(ViewState.idle);
|
||||
_logger.e('An unknown error occurred', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
|
||||
void openLink(String link) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:validators/sanitizers.dart';
|
||||
import 'package:validators/validators.dart';
|
||||
|
||||
import '../../core/services/file_service.dart';
|
||||
import '../../core/services/session_service.dart';
|
||||
import '../../core/services/storage_service.dart';
|
||||
import '../../locator.dart';
|
||||
|
@ -14,16 +14,18 @@ import '../enums/error_code.dart';
|
|||
import '../enums/viewstate.dart';
|
||||
import '../error/rest_service_exception.dart';
|
||||
import '../error/service_exception.dart';
|
||||
import '../models/rest/config.dart';
|
||||
import '../models/rest/create_apikey_response.dart';
|
||||
import '../services/user_service.dart';
|
||||
import '../util/logger.dart';
|
||||
import 'base_model.dart';
|
||||
|
||||
class LoginModel extends BaseModel {
|
||||
TextEditingController _uriController = new TextEditingController();
|
||||
final TextEditingController _userNameController = new TextEditingController();
|
||||
final TextEditingController _passwordController = new TextEditingController();
|
||||
TextEditingController _uriController = TextEditingController();
|
||||
|
||||
final TextEditingController _userNameController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
|
||||
final TextEditingController _apiKeyController = TextEditingController();
|
||||
|
||||
TextEditingController get uriController => _uriController;
|
||||
|
||||
|
@ -31,104 +33,145 @@ class LoginModel extends BaseModel {
|
|||
|
||||
TextEditingController get passwordController => _passwordController;
|
||||
|
||||
TextEditingController get apiKeyController => _apiKeyController;
|
||||
|
||||
final SessionService _sessionService = locator<SessionService>();
|
||||
final StorageService _storageService = locator<StorageService>();
|
||||
final UserService _userService = locator<UserService>();
|
||||
final FileService _fileService = locator<FileService>();
|
||||
final Logger _logger = getLogger();
|
||||
|
||||
String errorMessage;
|
||||
bool useCredentialsLogin = true;
|
||||
String? errorMessage;
|
||||
|
||||
void toggleLoginMethod() {
|
||||
setStateView(ViewState.busy);
|
||||
useCredentialsLogin = !useCredentialsLogin;
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
|
||||
void init() async {
|
||||
bool hasLastUrl = await _storageService.hasLastUrl();
|
||||
|
||||
if (hasLastUrl) {
|
||||
setState(ViewState.Busy);
|
||||
var s = await _storageService.retrieveLastUrl();
|
||||
setStateView(ViewState.busy);
|
||||
var s = await (_storageService.retrieveLastUrl() as FutureOr<String>);
|
||||
|
||||
if (s.isNotEmpty) {
|
||||
_uriController = new TextEditingController(text: s);
|
||||
_uriController = TextEditingController(text: s);
|
||||
}
|
||||
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> login(String url, String username, String password) async {
|
||||
setState(ViewState.Busy);
|
||||
Future<bool> login() async {
|
||||
var url = uriController.text;
|
||||
var username = userNameController.text;
|
||||
var password = passwordController.text;
|
||||
var apiKey = apiKeyController.text;
|
||||
|
||||
setStateView(ViewState.busy);
|
||||
url = trim(url);
|
||||
username = trim(username);
|
||||
|
||||
if (url.isEmpty) {
|
||||
errorMessage = translate('login.errors.empty_url');
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!url.contains("https://") && !url.contains("http://")) {
|
||||
errorMessage = translate('login.errors.no_protocol');
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool validUri = Uri.parse(url).isAbsolute;
|
||||
if (!validUri || !isURL(url)) {
|
||||
errorMessage = translate('login.errors.invalid_url');
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (username.isEmpty) {
|
||||
errorMessage = translate('login.errors.empty_username');
|
||||
setState(ViewState.Idle);
|
||||
return false;
|
||||
}
|
||||
if (useCredentialsLogin) {
|
||||
if (username.isEmpty) {
|
||||
errorMessage = translate('login.errors.empty_username');
|
||||
setStateView(ViewState.idle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (password.isEmpty) {
|
||||
errorMessage = translate('login.errors.empty_password');
|
||||
setState(ViewState.Idle);
|
||||
return false;
|
||||
if (password.isEmpty) {
|
||||
errorMessage = translate('login.errors.empty_password');
|
||||
setStateView(ViewState.idle);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (apiKey.isEmpty) {
|
||||
errorMessage = translate('login.errors.empty_apikey');
|
||||
setStateView(ViewState.idle);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var success = false;
|
||||
try {
|
||||
Config config = await _fileService.getConfig(url);
|
||||
CreateApiKeyResponse apiKeyResponse =
|
||||
await _userService.createApiKey(url, username, password, 'apikey', 'fbmobile');
|
||||
success = await _sessionService.login(url, apiKeyResponse.data['new_key'], config);
|
||||
if (useCredentialsLogin) {
|
||||
CreateApiKeyResponse apiKeyResponse = await _userService.createApiKey(
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
'apikey',
|
||||
'fbmobile-${DateTime.now().millisecondsSinceEpoch}');
|
||||
|
||||
var newKey = apiKeyResponse.data['new_key'];
|
||||
if (newKey != null) {
|
||||
success = await _sessionService.login(url, newKey);
|
||||
} else {
|
||||
throw ServiceException(
|
||||
code: ErrorCode.invalidApiKey,
|
||||
message: translate('login.errors.invalid_api_key'));
|
||||
}
|
||||
} else {
|
||||
_sessionService.setApiConfig(url, apiKey);
|
||||
await _userService.checkAccessLevelIsAtLeastApiKey();
|
||||
success = await _sessionService.login(url, apiKey);
|
||||
}
|
||||
errorMessage = null;
|
||||
} catch (e) {
|
||||
if (e is RestServiceException) {
|
||||
if (e.statusCode == HttpStatus.unauthorized) {
|
||||
errorMessage = translate('login.errors.wrong_credentials');
|
||||
} else if (e.statusCode != HttpStatus.unauthorized && e.statusCode == HttpStatus.forbidden) {
|
||||
} else if (e.statusCode != HttpStatus.unauthorized &&
|
||||
e.statusCode == HttpStatus.forbidden) {
|
||||
errorMessage = translate('login.errors.forbidden');
|
||||
} else if (e.statusCode == HttpStatus.notFound) {
|
||||
errorMessage = translate('api.incompatible_error_not_found');
|
||||
}
|
||||
if (e.statusCode == HttpStatus.badRequest) {
|
||||
errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message});
|
||||
errorMessage = translate('api.bad_request',
|
||||
args: {'reason': e.responseBody.message});
|
||||
} else {
|
||||
errorMessage = translate('api.general_rest_error');
|
||||
}
|
||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
||||
} else if (e is ServiceException && e.code == ErrorCode.invalidApiKey) {
|
||||
errorMessage = translate('login.errors.invalid_api_key');
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||
errorMessage = translate('api.socket_error');
|
||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||
errorMessage = translate('api.socket_timeout');
|
||||
} else {
|
||||
errorMessage = translate('app.unknown_error');
|
||||
_sessionService.logout();
|
||||
setState(ViewState.Idle);
|
||||
_logger.e('An unknown error occurred', e);
|
||||
throw e;
|
||||
setStateView(ViewState.idle);
|
||||
_logger.e('An unknown error occurred', error: e);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
if (errorMessage.isNotEmpty) {
|
||||
if (errorMessage!.isNotEmpty) {
|
||||
_sessionService.logout();
|
||||
}
|
||||
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
return success;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,106 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import '../../core/services/session_service.dart';
|
||||
import '../../locator.dart';
|
||||
import '../enums/error_code.dart';
|
||||
import '../error/rest_service_exception.dart';
|
||||
import '../error/service_exception.dart';
|
||||
import '../models/rest/config.dart';
|
||||
import '../services/dialog_service.dart';
|
||||
import '../services/file_service.dart';
|
||||
import '../services/link_service.dart';
|
||||
import '../util/formatter_util.dart';
|
||||
import '../util/logger.dart';
|
||||
import 'base_model.dart';
|
||||
|
||||
class ProfileModel extends BaseModel {
|
||||
static const _configurationButtonLoading = 'configurationButtonLoading';
|
||||
|
||||
final SessionService _sessionService = locator<SessionService>();
|
||||
final DialogService _dialogService = locator<DialogService>();
|
||||
final LinkService _linkService = locator<LinkService>();
|
||||
final FileService _fileService = locator<FileService>();
|
||||
final Logger _logger = getLogger();
|
||||
|
||||
bool get configLoading => getStateValueAsBoolean(_configurationButtonLoading);
|
||||
|
||||
String? errorMessage;
|
||||
|
||||
Future logout() async {
|
||||
var dialogResult = await _dialogService.showConfirmationDialog(
|
||||
title: translate('logout.title'), description: translate('logout.confirm'));
|
||||
title: translate('logout.title'),
|
||||
description: translate('logout.confirm'));
|
||||
|
||||
if (dialogResult.confirmed) {
|
||||
if (dialogResult.confirmed!) {
|
||||
await _sessionService.logout();
|
||||
}
|
||||
}
|
||||
|
||||
Future revealApiKey(String apiKey) async {
|
||||
Future revealApiKey(String? apiKey) async {
|
||||
await _dialogService.showDialog(
|
||||
title: translate('profile.revealed_api_key.title'),
|
||||
description: translate('profile.revealed_api_key.description', args: {'apiKey': apiKey}));
|
||||
description: translate('profile.revealed_api_key.description',
|
||||
args: {'apiKey': apiKey}));
|
||||
}
|
||||
|
||||
Future showConfig(String url) async {
|
||||
setStateBoolValue(_configurationButtonLoading, true);
|
||||
Config? config;
|
||||
try {
|
||||
config = await _fileService.getConfig(url);
|
||||
errorMessage = null;
|
||||
} catch (e) {
|
||||
if (e is RestServiceException) {
|
||||
if (e.statusCode == HttpStatus.unauthorized) {
|
||||
errorMessage = translate('login.errors.wrong_credentials');
|
||||
} else if (e.statusCode != HttpStatus.unauthorized &&
|
||||
e.statusCode == HttpStatus.forbidden) {
|
||||
errorMessage = translate('login.errors.forbidden');
|
||||
} else if (e.statusCode == HttpStatus.notFound) {
|
||||
errorMessage = translate('api.incompatible_error_not_found');
|
||||
}
|
||||
if (e.statusCode == HttpStatus.badRequest) {
|
||||
errorMessage = translate('api.bad_request',
|
||||
args: {'reason': e.responseBody.message});
|
||||
} else {
|
||||
errorMessage = translate('api.general_rest_error');
|
||||
}
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||
errorMessage = translate('api.socket_error');
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||
errorMessage = translate('api.socket_timeout');
|
||||
} else {
|
||||
errorMessage = translate('app.unknown_error');
|
||||
setStateBoolValue(_configurationButtonLoading, false);
|
||||
_sessionService.logout();
|
||||
setStateBoolValue(_configurationButtonLoading, false);
|
||||
_logger.e('An unknown error occurred', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
setStateBoolValue(_configurationButtonLoading, false);
|
||||
|
||||
if (config != null && errorMessage == null) {
|
||||
await _dialogService.showDialog(
|
||||
title: translate('profile.shown_config.title'),
|
||||
description: translate('profile.shown_config.description', args: {
|
||||
'uploadMaxSize':
|
||||
FormatterUtil.formatBytes(config.uploadMaxSize as int, 2),
|
||||
'maxFilesPerRequest': config.maxFilesPerRequest,
|
||||
'maxInputVars': config.maxInputVars,
|
||||
'requestMaxSize':
|
||||
FormatterUtil.formatBytes(config.requestMaxSize as int, 2)
|
||||
}));
|
||||
} else {
|
||||
await _dialogService.showDialog(
|
||||
title: translate('profile.shown_config.error.title'),
|
||||
description: translate('profile.shown_config.error.description',
|
||||
args: {'message': errorMessage}));
|
||||
}
|
||||
}
|
||||
|
||||
void openLink(String link) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:fbmobile/core/services/permission_service.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../../locator.dart';
|
||||
|
@ -9,19 +10,21 @@ import 'base_model.dart';
|
|||
|
||||
class StartUpViewModel extends BaseModel {
|
||||
final SessionService _sessionService = locator<SessionService>();
|
||||
final PermissionService _permissionService = locator<PermissionService>();
|
||||
final NavigationService _navigationService = locator<NavigationService>();
|
||||
|
||||
Future handleStartUpLogic() async {
|
||||
setState(ViewState.Busy);
|
||||
setStateView(ViewState.busy);
|
||||
setStateMessage(translate('startup.init'));
|
||||
await Future.delayed(Duration(milliseconds: 150));
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
setStateMessage(translate('startup.start_services'));
|
||||
await _sessionService.start();
|
||||
await Future.delayed(Duration(milliseconds: 150));
|
||||
await _permissionService.start();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
_navigationService.navigateAndReplaceTo(HomeView.routeName);
|
||||
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,11 @@ import 'dart:io';
|
|||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
||||
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
|
||||
import '../../locator.dart';
|
||||
import '../enums/error_code.dart';
|
||||
|
@ -22,6 +23,7 @@ import '../services/file_service.dart';
|
|||
import '../services/link_service.dart';
|
||||
import '../services/refresh_service.dart';
|
||||
import '../util/logger.dart';
|
||||
import '../util/paste_util.dart';
|
||||
import 'base_model.dart';
|
||||
|
||||
class UploadModel extends BaseModel {
|
||||
|
@ -30,88 +32,111 @@ class UploadModel extends BaseModel {
|
|||
final LinkService _linkService = locator<LinkService>();
|
||||
final RefreshService _refreshService = locator<RefreshService>();
|
||||
|
||||
TextEditingController _pasteTextController = TextEditingController();
|
||||
StreamSubscription _intentDataStreamSubscription;
|
||||
final TextEditingController _pasteTextController = TextEditingController();
|
||||
bool pasteTextTouched = false;
|
||||
|
||||
late StreamSubscription _intentDataStreamSubscription;
|
||||
|
||||
bool createMulti = false;
|
||||
String fileName;
|
||||
List<PlatformFile> paths;
|
||||
String _extension;
|
||||
String? fileName;
|
||||
List<PlatformFile>? paths;
|
||||
String? _extension;
|
||||
bool loadingPath = false;
|
||||
String errorMessage;
|
||||
String? errorMessage;
|
||||
|
||||
TextEditingController get pasteTextController => _pasteTextController;
|
||||
|
||||
void _parseIntentFiles(List<SharedFile> files) {
|
||||
if (files.isNotEmpty) {
|
||||
setStateView(ViewState.busy);
|
||||
|
||||
paths = files.map((sharedFile) {
|
||||
_logger.d("Shared file name: ${basename(sharedFile.value ?? '')}");
|
||||
_logger.d("Shared file path: ${sharedFile.value}");
|
||||
_logger.d(
|
||||
"Shared file size: ${File(sharedFile.value ?? '').lengthSync()}");
|
||||
_logger.d("Shared file type: ${sharedFile.type}");
|
||||
return PlatformFile.fromMap({
|
||||
'path': sharedFile.value,
|
||||
'name': basename(sharedFile.value!),
|
||||
'size': File(sharedFile.value!).lengthSync(),
|
||||
'bytes': null
|
||||
});
|
||||
}).toList();
|
||||
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
}
|
||||
|
||||
void deleteIntentFile(String path) {
|
||||
setStateView(ViewState.busy);
|
||||
_logger.d("Removing path '$path' from $paths");
|
||||
|
||||
paths?.removeWhere((element) => element.path == path);
|
||||
|
||||
int length = paths!.length;
|
||||
if (length == 0) {
|
||||
paths = null;
|
||||
}
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
|
||||
void init() {
|
||||
_pasteTextController.addListener(() {
|
||||
pasteTextTouched = pasteTextController.text.isNotEmpty;
|
||||
setStateBoolValue("PASTE_TEXT_TOUCHED", pasteTextTouched);
|
||||
});
|
||||
|
||||
// For sharing images coming from outside the app while the app is in the memory
|
||||
_intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream().listen((List<SharedMediaFile> value) {
|
||||
if (value != null && value.length > 0) {
|
||||
setState(ViewState.Busy);
|
||||
paths = value.map((sharedFile) {
|
||||
return PlatformFile.fromMap({
|
||||
'path': sharedFile.path,
|
||||
'name': basename(sharedFile.path),
|
||||
'size': File(sharedFile.path).lengthSync(),
|
||||
'bytes': null
|
||||
});
|
||||
}).toList();
|
||||
setState(ViewState.Idle);
|
||||
}
|
||||
_intentDataStreamSubscription = FlutterSharingIntent.instance
|
||||
.getMediaStream()
|
||||
.listen((List<SharedFile> value) {
|
||||
_logger.d("Retrieved ${value.length} files from intent");
|
||||
_parseIntentFiles(value);
|
||||
}, onError: (err) {
|
||||
setState(ViewState.Busy);
|
||||
errorMessage = translate('upload.retrieval_intent');
|
||||
_logger.e('Error while retrieving shared data: $err');
|
||||
setState(ViewState.Idle);
|
||||
_errorIntentHandle(err);
|
||||
});
|
||||
|
||||
// For sharing images coming from outside the app while the app is closed
|
||||
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
|
||||
if (value != null && value.length > 0) {
|
||||
setState(ViewState.Busy);
|
||||
paths = value.map((sharedFile) {
|
||||
return PlatformFile.fromMap({
|
||||
'path': sharedFile.path,
|
||||
'name': basename(sharedFile.path),
|
||||
'size': File(sharedFile.path).lengthSync(),
|
||||
'bytes': null
|
||||
});
|
||||
}).toList();
|
||||
setState(ViewState.Idle);
|
||||
}
|
||||
FlutterSharingIntent.instance
|
||||
.getInitialSharing()
|
||||
.then((List<SharedFile> value) {
|
||||
_logger.d("Retrieved ${value.length} files from inactive intent");
|
||||
_parseIntentFiles(value);
|
||||
});
|
||||
}
|
||||
|
||||
// For sharing or opening urls/text coming from outside the app while the app is in the memory
|
||||
_intentDataStreamSubscription = ReceiveSharingIntent.getTextStream().listen((String value) {
|
||||
if (value != null && value.isNotEmpty) {
|
||||
setState(ViewState.Busy);
|
||||
pasteTextController.text = value;
|
||||
setState(ViewState.Idle);
|
||||
}
|
||||
}, onError: (err) {
|
||||
setState(ViewState.Busy);
|
||||
errorMessage = translate('upload.retrieval_intent');
|
||||
_logger.e('Error while retrieving shared data: $err');
|
||||
setState(ViewState.Idle);
|
||||
});
|
||||
void _errorIntentHandle(err) {
|
||||
setStateView(ViewState.busy);
|
||||
errorMessage = translate('upload.retrieval_intent');
|
||||
_logger.e('Error while retrieving shared data: $err');
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
|
||||
// For sharing or opening urls/text coming from outside the app while the app is closed
|
||||
ReceiveSharingIntent.getInitialText().then((String value) {
|
||||
if (value != null && value.isNotEmpty) {
|
||||
setState(ViewState.Busy);
|
||||
pasteTextController.text = value;
|
||||
setState(ViewState.Idle);
|
||||
}
|
||||
});
|
||||
String? generatePasteLinks(Map<String, bool>? uploads, String url) {
|
||||
if (uploads != null && uploads.isNotEmpty) {
|
||||
var links = '';
|
||||
|
||||
uploads.forEach((id, isMulti) {
|
||||
if (isMulti && createMulti || !isMulti && !createMulti) {
|
||||
links += '${PasteUtil.generateLink(url, id)}\n';
|
||||
}
|
||||
});
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void toggleCreateMulti() {
|
||||
setState(ViewState.Busy);
|
||||
setStateView(ViewState.busy);
|
||||
createMulti = !createMulti;
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
|
||||
void openFileExplorer() async {
|
||||
setState(ViewState.Busy);
|
||||
setStateView(ViewState.busy);
|
||||
setStateMessage(translate('upload.file_explorer_open'));
|
||||
loadingPath = true;
|
||||
|
||||
|
@ -121,60 +146,68 @@ class UploadModel extends BaseModel {
|
|||
allowMultiple: true,
|
||||
withData: false,
|
||||
withReadStream: true,
|
||||
allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '')?.split(',') : null,
|
||||
allowedExtensions: (_extension?.isNotEmpty ?? false)
|
||||
? _extension?.replaceAll(' ', '').split(',')
|
||||
: null,
|
||||
))
|
||||
?.files;
|
||||
} on PlatformException catch (e) {
|
||||
_logger.e('Unsupported operation', e);
|
||||
_logger.e('Unsupported operation', error: e);
|
||||
} catch (ex) {
|
||||
_logger.e('An unknown error occurred', ex);
|
||||
_logger.e('An unknown error occurred', error: ex);
|
||||
}
|
||||
|
||||
loadingPath = false;
|
||||
fileName = paths != null ? paths.map((e) => e.name).toString() : '...';
|
||||
fileName = paths != null ? paths!.map((e) => e.name).toString() : '...';
|
||||
|
||||
setStateMessage(null);
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
|
||||
void clearCachedFiles() async {
|
||||
setState(ViewState.Busy);
|
||||
setStateView(ViewState.busy);
|
||||
await FilePicker.platform.clearTemporaryFiles();
|
||||
paths = null;
|
||||
fileName = null;
|
||||
errorMessage = null;
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
}
|
||||
|
||||
Future<List<String>> upload() async {
|
||||
setState(ViewState.Busy);
|
||||
Future<Map<String, bool>?> upload() async {
|
||||
setStateView(ViewState.busy);
|
||||
setStateMessage(translate('upload.uploading_now'));
|
||||
|
||||
List<String> uploadedPasteIds = [];
|
||||
Map<String, bool> uploadedPasteIds = {};
|
||||
try {
|
||||
List<File> files;
|
||||
Map<String, String> additionalFiles;
|
||||
List<File>? files;
|
||||
Map<String, String>? additionalFiles;
|
||||
|
||||
if (pasteTextController.text != null && pasteTextController.text.isNotEmpty) {
|
||||
additionalFiles = Map.from(
|
||||
{'paste-${(new DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt': pasteTextController.text});
|
||||
if (pasteTextController.text.isNotEmpty) {
|
||||
additionalFiles = Map.from({
|
||||
'paste-${(DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt':
|
||||
pasteTextController.text
|
||||
});
|
||||
}
|
||||
|
||||
if (paths != null && paths.length > 0) {
|
||||
files = paths.map((e) => new File(e.path)).toList();
|
||||
if (paths != null && paths!.isNotEmpty) {
|
||||
files = paths!.map((e) => File(e.path!)).toList();
|
||||
}
|
||||
|
||||
UploadedResponse response = await _fileService.upload(files, additionalFiles);
|
||||
uploadedPasteIds.addAll(response.data.ids);
|
||||
UploadedResponse response =
|
||||
await _fileService.uploadPaste(files, additionalFiles);
|
||||
for (var element in response.data.ids) {
|
||||
uploadedPasteIds.putIfAbsent(element, () => false);
|
||||
}
|
||||
|
||||
if (createMulti && response.data.ids.length > 1) {
|
||||
UploadedMultiResponse multiResponse = await _fileService.createMulti(response.data.ids);
|
||||
uploadedPasteIds.add(multiResponse.data.urlId);
|
||||
UploadedMultiResponse multiResponse =
|
||||
await _fileService.uploadMultiPaste(response.data.ids);
|
||||
uploadedPasteIds.putIfAbsent(multiResponse.data.urlId, () => true);
|
||||
}
|
||||
|
||||
clearCachedFiles();
|
||||
_pasteTextController.clear();
|
||||
_refreshService.addEvent(RefreshEvent.RefreshHistory);
|
||||
_refreshService.addEvent(RefreshEvent.refreshHistory);
|
||||
errorMessage = null;
|
||||
return uploadedPasteIds;
|
||||
} catch (e) {
|
||||
|
@ -188,28 +221,30 @@ class UploadModel extends BaseModel {
|
|||
e.responseBody is RestError &&
|
||||
e.responseBody.message != null) {
|
||||
if (e.statusCode == HttpStatus.badRequest) {
|
||||
errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message});
|
||||
errorMessage = translate('api.bad_request',
|
||||
args: {'reason': e.responseBody.message});
|
||||
} else {
|
||||
errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message});
|
||||
errorMessage = translate('api.general_rest_error_payload',
|
||||
args: {'message': e.responseBody.message});
|
||||
}
|
||||
} else {
|
||||
errorMessage = translate('api.general_rest_error');
|
||||
}
|
||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||
errorMessage = translate('api.socket_error');
|
||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||
errorMessage = translate('api.socket_timeout');
|
||||
} else {
|
||||
errorMessage = translate('app.unknown_error');
|
||||
setStateMessage(null);
|
||||
setState(ViewState.Idle);
|
||||
_logger.e('An unknown error occurred', e);
|
||||
throw e;
|
||||
setStateView(ViewState.idle);
|
||||
_logger.e('An unknown error occurred', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
setStateMessage(null);
|
||||
setState(ViewState.Idle);
|
||||
setStateView(ViewState.idle);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -221,6 +256,8 @@ class UploadModel extends BaseModel {
|
|||
void dispose() {
|
||||
_pasteTextController.dispose();
|
||||
_intentDataStreamSubscription.cancel();
|
||||
FlutterSharingIntent.instance.reset();
|
||||
paths = null;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ import 'locator.dart';
|
|||
/// main entry point used to configure log level, locales, ...
|
||||
void main() async {
|
||||
setupLogger(Level.info);
|
||||
// setupLogger(Level.debug);
|
||||
setupLocator();
|
||||
|
||||
var delegate = await LocalizationDelegate.create(fallbackLocale: 'en', supportedLocales: ['en']);
|
||||
var delegate = await LocalizationDelegate.create(
|
||||
fallbackLocale: 'en', supportedLocales: ['en', 'en_US']);
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(LocalizedApp(delegate, MyApp()));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../ui/views/tabbar_container_view.dart';
|
||||
|
@ -15,20 +14,21 @@ class AppRouter {
|
|||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||
switch (settings.name) {
|
||||
case StartUpView.routeName:
|
||||
return MaterialPageRoute(builder: (_) => StartUpView());
|
||||
return MaterialPageRoute(builder: (_) => const StartUpView());
|
||||
case AboutView.routeName:
|
||||
return MaterialPageRoute(builder: (_) => AboutView());
|
||||
return MaterialPageRoute(builder: (_) => const AboutView());
|
||||
case HomeView.routeName:
|
||||
return MaterialPageRoute(builder: (_) => TabBarContainerView());
|
||||
return MaterialPageRoute(builder: (_) => const TabBarContainerView());
|
||||
case LoginView.routeName:
|
||||
return MaterialPageRoute(builder: (_) => LoginView());
|
||||
case ProfileView.routeName:
|
||||
return MaterialPageRoute(builder: (_) => ProfileView());
|
||||
return MaterialPageRoute(builder: (_) => const ProfileView());
|
||||
default:
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => Scaffold(
|
||||
body: Center(
|
||||
child: Text(translate('dev.no_route', args: {'route': settings.name})),
|
||||
child: Text(translate('dev.no_route',
|
||||
args: {'route': settings.name})),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
const Color backgroundColor = Colors.white;
|
||||
|
||||
/// Colors
|
||||
const Color primaryBackgroundColor = Colors.white;
|
||||
|
||||
const Map<int, Color> colors = {
|
||||
50: Color.fromRGBO(63, 69, 75, .1),
|
||||
100: Color.fromRGBO(63, 69, 75, .2),
|
||||
|
@ -19,5 +14,9 @@ const Map<int, Color> colors = {
|
|||
};
|
||||
const MaterialColor myColor = MaterialColor(0xFF3F454B, colors);
|
||||
const Color primaryAccentColor = myColor;
|
||||
const Color buttonBackgroundColor = primaryAccentColor;
|
||||
const Color buttonForegroundColor = Colors.white;
|
||||
|
||||
const Color blueColor = Colors.blue;
|
||||
const Color whiteColor = Colors.white;
|
||||
const Color redColor = Colors.red;
|
||||
const Color orangeColor = Colors.orange;
|
||||
const Color greenColor = Colors.green;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
const headerStyle = TextStyle(fontSize: 35, fontWeight: FontWeight.w900);
|
||||
const subHeaderStyle = TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500);
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class UIHelper {
|
||||
static const double _VerticalSpaceSmall = 10.0;
|
||||
static const double _VerticalSpaceMedium = 20.0;
|
||||
static const double _VerticalSpaceLarge = 60.0;
|
||||
static const double _verticalSpaceSmall = 10.0;
|
||||
static const double _verticalSpaceMedium = 20.0;
|
||||
static const double _verticalSpaceLarge = 60.0;
|
||||
|
||||
static const double _HorizontalSpaceSmall = 10.0;
|
||||
static const double _HorizontalSpaceMedium = 20.0;
|
||||
static const double HorizontalSpaceLarge = 60.0;
|
||||
static const double _horizontalSpaceSmall = 10.0;
|
||||
static const double _horizontalSpaceMedium = 20.0;
|
||||
static const double _horizontalSpaceLarge = 60.0;
|
||||
|
||||
/// Returns a vertical space with height set to [_VerticalSpaceSmall]
|
||||
/// Returns a vertical space with height set to [_verticalSpaceSmall]
|
||||
static Widget verticalSpaceSmall() {
|
||||
return verticalSpace(_VerticalSpaceSmall);
|
||||
return verticalSpace(_verticalSpaceSmall);
|
||||
}
|
||||
|
||||
/// Returns a vertical space with height set to [_VerticalSpaceMedium]
|
||||
/// Returns a vertical space with height set to [_verticalSpaceMedium]
|
||||
static Widget verticalSpaceMedium() {
|
||||
return verticalSpace(_VerticalSpaceMedium);
|
||||
return verticalSpace(_verticalSpaceMedium);
|
||||
}
|
||||
|
||||
/// Returns a vertical space with height set to [_VerticalSpaceLarge]
|
||||
/// Returns a vertical space with height set to [_verticalSpaceLarge]
|
||||
static Widget verticalSpaceLarge() {
|
||||
return verticalSpace(_VerticalSpaceLarge);
|
||||
return verticalSpace(_verticalSpaceLarge);
|
||||
}
|
||||
|
||||
/// Returns a vertical space equal to the [height] supplied
|
||||
|
@ -29,19 +29,19 @@ class UIHelper {
|
|||
return Container(height: height);
|
||||
}
|
||||
|
||||
/// Returns a vertical space with height set to [_HorizontalSpaceSmall]
|
||||
/// Returns a vertical space with height set to [_horizontalSpaceSmall]
|
||||
static Widget horizontalSpaceSmall() {
|
||||
return horizontalSpace(_HorizontalSpaceSmall);
|
||||
return horizontalSpace(_horizontalSpaceSmall);
|
||||
}
|
||||
|
||||
/// Returns a vertical space with height set to [_HorizontalSpaceMedium]
|
||||
/// Returns a vertical space with height set to [_horizontalSpaceMedium]
|
||||
static Widget horizontalSpaceMedium() {
|
||||
return horizontalSpace(_HorizontalSpaceMedium);
|
||||
return horizontalSpace(_horizontalSpaceMedium);
|
||||
}
|
||||
|
||||
/// Returns a vertical space with height set to [HorizontalSpaceLarge]
|
||||
/// Returns a vertical space with height set to [_horizontalSpaceLarge]
|
||||
static Widget horizontalSpaceLarge() {
|
||||
return horizontalSpace(HorizontalSpaceLarge);
|
||||
return horizontalSpace(_horizontalSpaceLarge);
|
||||
}
|
||||
|
||||
/// Returns a vertical space equal to the [width] supplied
|
||||
|
|
|
@ -6,13 +6,14 @@ import '../../core/enums/viewstate.dart';
|
|||
import '../../core/viewmodels/about_model.dart';
|
||||
import '../../ui/shared/text_styles.dart';
|
||||
import '../../ui/shared/ui_helpers.dart';
|
||||
import '../shared/app_colors.dart';
|
||||
import '../widgets/my_appbar.dart';
|
||||
import 'base_view.dart';
|
||||
|
||||
class AboutView extends StatelessWidget {
|
||||
static const routeName = '/about';
|
||||
|
||||
const AboutView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final logo = Hero(
|
||||
|
@ -25,31 +26,42 @@ class AboutView extends StatelessWidget {
|
|||
);
|
||||
|
||||
return BaseView<AboutModel>(
|
||||
onModelReady: (model) => model.init(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(
|
||||
title: Text(translate('titles.about')),
|
||||
enableAbout: false,
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
body: model.state == ViewState.Busy
|
||||
? Center(child: CircularProgressIndicator())
|
||||
body: model.state == ViewState.busy
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Container(
|
||||
padding: EdgeInsets.all(0),
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10.0, right: 10.0, bottom: 10, top: 10),
|
||||
children: <Widget>[
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Center(child: logo),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Center(
|
||||
child: Text(
|
||||
translate(('about.description')),
|
||||
translate('about.versions', args: {
|
||||
'appName': model.packageInfo.appName,
|
||||
'packageName': model.packageInfo.packageName,
|
||||
'version': model.packageInfo.version,
|
||||
'buildNumber': model.packageInfo.buildNumber
|
||||
}),
|
||||
)),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Center(
|
||||
child: Text(
|
||||
translate(('about.faq_headline')),
|
||||
translate('about.description'),
|
||||
)),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Center(
|
||||
child: Text(
|
||||
translate('about.faq_headline'),
|
||||
style: subHeaderStyle,
|
||||
)),
|
||||
Center(
|
||||
|
@ -59,14 +71,14 @@ class AboutView extends StatelessWidget {
|
|||
UIHelper.verticalSpaceMedium(),
|
||||
Center(
|
||||
child: Text(
|
||||
translate(('about.contact_us')),
|
||||
translate('about.contact_us'),
|
||||
style: subHeaderStyle,
|
||||
)),
|
||||
UIHelper.verticalSpaceSmall(),
|
||||
Center(
|
||||
child: Linkify(
|
||||
text: translate('about.website'),
|
||||
options: LinkifyOptions(humanize: false),
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
onOpen: (link) => model.openLink(link.url),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -7,10 +7,10 @@ import '../../core/viewmodels/base_model.dart';
|
|||
import '../../locator.dart';
|
||||
|
||||
class BaseView<T extends BaseModel> extends StatefulWidget {
|
||||
final Widget Function(BuildContext context, T model, Widget child) builder;
|
||||
final Function(T) onModelReady;
|
||||
final Widget Function(BuildContext context, T model, Widget? child)? builder;
|
||||
final Function(T)? onModelReady;
|
||||
|
||||
BaseView({this.builder, this.onModelReady});
|
||||
const BaseView({super.key, this.builder, this.onModelReady});
|
||||
|
||||
@override
|
||||
_BaseViewState<T> createState() => _BaseViewState<T>();
|
||||
|
@ -24,14 +24,16 @@ class _BaseViewState<T extends BaseModel> extends State<BaseView<T>> {
|
|||
@override
|
||||
void initState() {
|
||||
if (widget.onModelReady != null) {
|
||||
widget.onModelReady(model);
|
||||
widget.onModelReady!(model);
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider<T>(create: (context) => model, child: Consumer<T>(builder: widget.builder));
|
||||
return ChangeNotifierProvider<T?>(
|
||||
create: (context) => model,
|
||||
child: Consumer<T>(builder: widget.builder!));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share/share.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../../core/enums/viewstate.dart';
|
||||
import '../../core/models/session.dart';
|
||||
import '../../core/util/formatter_util.dart';
|
||||
import '../../core/util/paste_util.dart';
|
||||
import '../../core/viewmodels/history_model.dart';
|
||||
import '../../ui/widgets/centered_error_row.dart';
|
||||
import '../shared/app_colors.dart';
|
||||
|
@ -16,10 +18,10 @@ import 'base_view.dart';
|
|||
class HistoryView extends StatelessWidget {
|
||||
static const routeName = '/history';
|
||||
|
||||
const HistoryView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var url = Provider.of<Session>(context).url;
|
||||
|
||||
return BaseView<HistoryModel>(
|
||||
onModelReady: (model) {
|
||||
model.init();
|
||||
|
@ -27,34 +29,42 @@ class HistoryView extends StatelessWidget {
|
|||
},
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(title: Text(translate('titles.history'))),
|
||||
backgroundColor: backgroundColor,
|
||||
body: model.state == ViewState.Busy
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: (model.errorMessage == null
|
||||
? Container(
|
||||
padding: EdgeInsets.all(0),
|
||||
child: RefreshIndicator(onRefresh: () => model.getHistory(), child: _render(model, url)))
|
||||
: Container(
|
||||
padding: EdgeInsets.all(25),
|
||||
child: CenteredErrorRow(
|
||||
model.errorMessage,
|
||||
retryCallback: () => model.getHistory(),
|
||||
)))),
|
||||
body: _render(model, context)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _render(HistoryModel model, String url) {
|
||||
List<Widget> cards = List<Widget>();
|
||||
Widget _render(HistoryModel model, BuildContext context) {
|
||||
var url = Provider.of<Session>(context).url;
|
||||
|
||||
if (model.pastes.length > 0) {
|
||||
model.pastes.reversed.forEach((paste) {
|
||||
return model.state == ViewState.busy
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: (model.errorMessage == null
|
||||
? Container(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async => await model.getHistory(),
|
||||
child: _renderItems(model, url, context)))
|
||||
: Container(
|
||||
padding: const EdgeInsets.all(25),
|
||||
child: CenteredErrorRow(
|
||||
model.errorMessage,
|
||||
retryCallback: () => model.getHistory(),
|
||||
)));
|
||||
}
|
||||
|
||||
Widget _renderItems(HistoryModel model, String url, BuildContext context) {
|
||||
List<Widget> cards = [];
|
||||
|
||||
if (model.pastes.isNotEmpty) {
|
||||
for (var paste in model.pastes.reversed) {
|
||||
List<Widget> widgets = [];
|
||||
|
||||
var fullPasteUrl = '$url/${paste.id}';
|
||||
var fullPasteUrl = PasteUtil.generateLink(url, paste.id);
|
||||
var openInBrowserButton = _renderOpenInBrowser(model, fullPasteUrl);
|
||||
|
||||
var dateWidget = ListTile(
|
||||
title: Text(FormatterUtil.formatEpoch(paste.date.millisecondsSinceEpoch)),
|
||||
title: Text(
|
||||
FormatterUtil.formatEpoch(paste.date!.millisecondsSinceEpoch)),
|
||||
subtitle: Text(translate('history.date')),
|
||||
);
|
||||
|
||||
|
@ -63,21 +73,45 @@ class HistoryView extends StatelessWidget {
|
|||
trailing: openInBrowserButton,
|
||||
);
|
||||
|
||||
var copyWidget = ListTile(
|
||||
title: Text(translate('history.copy_link.description')),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.copy,
|
||||
color: blueColor, textDirection: TextDirection.ltr),
|
||||
onPressed: () {
|
||||
FlutterClipboard.copy(fullPasteUrl).then((value) {
|
||||
final snackBar = SnackBar(
|
||||
action: SnackBarAction(
|
||||
label: translate('history.copy_link.dismiss'),
|
||||
textColor: blueColor,
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
},
|
||||
),
|
||||
content: Text(translate('history.copy_link.copied')),
|
||||
duration: const Duration(seconds: 10),
|
||||
);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
var deleteWidget = ListTile(
|
||||
title: Text(translate('history.delete')),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.delete, color: Colors.red),
|
||||
icon: const Icon(Icons.delete, color: redColor),
|
||||
onPressed: () {
|
||||
return model.deletePaste(paste.id);
|
||||
model.deletePaste(paste.id);
|
||||
}));
|
||||
|
||||
if (!paste.isMulti) {
|
||||
if (!paste.isMulti!) {
|
||||
var titleWidget = ListTile(
|
||||
title: Text(paste.filename ?? paste.id),
|
||||
subtitle: Text(translate('history.filename')),
|
||||
);
|
||||
var fileSizeWidget = ListTile(
|
||||
title: Text(FormatterUtil.formatBytes(paste.filesize, 2)),
|
||||
title: Text(FormatterUtil.formatBytes(paste.filesize as int, 2)),
|
||||
subtitle: Text(translate('history.filesize')),
|
||||
);
|
||||
var idWidget = ListTile(
|
||||
|
@ -85,7 +119,7 @@ class HistoryView extends StatelessWidget {
|
|||
subtitle: Text(translate('history.id')),
|
||||
);
|
||||
var mimeTypeWidget = ListTile(
|
||||
title: Text(paste.mimetype),
|
||||
title: Text(paste.mimetype!),
|
||||
subtitle: Text(translate('history.mimetype')),
|
||||
);
|
||||
|
||||
|
@ -94,34 +128,35 @@ class HistoryView extends StatelessWidget {
|
|||
widgets.add(fileSizeWidget);
|
||||
widgets.add(mimeTypeWidget);
|
||||
} else {
|
||||
paste.items.forEach((element) {
|
||||
for (var element in paste.items!) {
|
||||
widgets.add(ListTile(
|
||||
title: Text(element),
|
||||
title: Text(element!),
|
||||
subtitle: Text(translate('history.multipaste_element')),
|
||||
trailing: _renderOpenInBrowser(model, '$url/$element'),
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
widgets.add(dateWidget);
|
||||
widgets.add(linkWidget);
|
||||
widgets.add(copyWidget);
|
||||
widgets.add(deleteWidget);
|
||||
|
||||
var expandable = ExpandableTheme(
|
||||
data: ExpandableThemeData(
|
||||
data: const ExpandableThemeData(
|
||||
iconPlacement: ExpandablePanelIconPlacement.right,
|
||||
headerAlignment: ExpandablePanelHeaderAlignment.center,
|
||||
hasIcon: true,
|
||||
iconColor: Colors.blue,
|
||||
iconColor: blueColor,
|
||||
tapHeaderToExpand: true),
|
||||
child: ExpandablePanel(
|
||||
collapsed: Container(),
|
||||
header: InkWell(
|
||||
onLongPress: () => model.deletePaste(paste.id),
|
||||
child: Text(
|
||||
paste.id,
|
||||
style: TextStyle(color: Colors.blue),
|
||||
textAlign: TextAlign.left,
|
||||
)),
|
||||
paste.id,
|
||||
style: const TextStyle(color: blueColor),
|
||||
textAlign: TextAlign.left,
|
||||
)),
|
||||
expanded: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: widgets,
|
||||
|
@ -135,15 +170,17 @@ class HistoryView extends StatelessWidget {
|
|||
trailing: Wrap(children: [
|
||||
openInBrowserButton,
|
||||
IconButton(
|
||||
icon: Icon(Icons.share, color: Colors.blue, textDirection: TextDirection.ltr),
|
||||
onPressed: () {
|
||||
return Share.share(fullPasteUrl);
|
||||
icon: const Icon(Icons.share,
|
||||
color: blueColor, textDirection: TextDirection.ltr),
|
||||
onPressed: () async {
|
||||
await Share.share(fullPasteUrl);
|
||||
})
|
||||
]),
|
||||
subtitle: Text(!paste.isMulti ? paste.filename : '', style: TextStyle(fontStyle: FontStyle.italic)),
|
||||
subtitle: Text(!paste.isMulti! ? paste.filename! : '',
|
||||
style: const TextStyle(fontStyle: FontStyle.italic)),
|
||||
),
|
||||
));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cards.add(Card(
|
||||
child: ListTile(
|
||||
|
@ -154,14 +191,15 @@ class HistoryView extends StatelessWidget {
|
|||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: cards,
|
||||
physics: AlwaysScrollableScrollPhysics(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderOpenInBrowser(HistoryModel model, String url) {
|
||||
return IconButton(
|
||||
icon: Icon(Icons.open_in_new, color: Colors.blue, textDirection: TextDirection.ltr),
|
||||
icon: const Icon(Icons.open_in_new,
|
||||
color: blueColor, textDirection: TextDirection.ltr),
|
||||
onPressed: () {
|
||||
return model.openLink(url);
|
||||
});
|
||||
|
|
|
@ -3,20 +3,22 @@ import 'package:flutter_translate/flutter_translate.dart';
|
|||
|
||||
import '../../core/enums/viewstate.dart';
|
||||
import '../../core/viewmodels/home_model.dart';
|
||||
import '../shared/app_colors.dart';
|
||||
import '../widgets/my_appbar.dart';
|
||||
import 'base_view.dart';
|
||||
|
||||
class HomeView extends StatelessWidget {
|
||||
static const routeName = '/home';
|
||||
|
||||
const HomeView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BaseView<HomeModel>(
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(title: Text(translate('app.title'))),
|
||||
backgroundColor: backgroundColor,
|
||||
body: model.state == ViewState.Busy ? Center(child: CircularProgressIndicator()) : Container()),
|
||||
body: model.state == ViewState.busy
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Container()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,31 +6,29 @@ import '../../core/services/dialog_service.dart';
|
|||
import '../../core/services/navigation_service.dart';
|
||||
import '../../core/viewmodels/login_model.dart';
|
||||
import '../../locator.dart';
|
||||
import '../../ui/shared/text_styles.dart';
|
||||
import '../../ui/views/home_view.dart';
|
||||
import '../../ui/widgets/my_appbar.dart';
|
||||
import '../shared/app_colors.dart';
|
||||
import '../widgets/login_header.dart';
|
||||
import '../shared/ui_helpers.dart';
|
||||
import '../widgets/login_header_apikey.dart';
|
||||
import '../widgets/login_header_credentials.dart';
|
||||
import 'base_view.dart';
|
||||
|
||||
class LoginView extends StatefulWidget {
|
||||
class LoginView extends StatelessWidget {
|
||||
static const routeName = '/login';
|
||||
|
||||
@override
|
||||
_LoginViewState createState() => _LoginViewState();
|
||||
}
|
||||
|
||||
class _LoginViewState extends State<LoginView> {
|
||||
final NavigationService _navigationService = locator<NavigationService>();
|
||||
final DialogService _dialogService = locator<DialogService>();
|
||||
|
||||
LoginView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final logo = Hero(
|
||||
tag: 'hero',
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
radius: 96.0,
|
||||
radius: 36.0,
|
||||
child: Image.asset('assets/logo_caption.png'),
|
||||
),
|
||||
);
|
||||
|
@ -39,50 +37,62 @@ class _LoginViewState extends State<LoginView> {
|
|||
onModelReady: (model) => model.init(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(title: Text(translate('titles.login'))),
|
||||
backgroundColor: backgroundColor,
|
||||
body: model.state == ViewState.Busy
|
||||
? Center(child: CircularProgressIndicator())
|
||||
body: model.state == ViewState.busy
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
|
||||
children: <Widget>[
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Center(child: logo),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Center(
|
||||
child: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
alignment: WrapAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
translate('login.help'),
|
||||
style: subHeaderStyle,
|
||||
),
|
||||
InkWell(
|
||||
child: Icon(Icons.help, color: buttonBackgroundColor),
|
||||
child: const Icon(Icons.help),
|
||||
onTap: () {
|
||||
_dialogService.showDialog(
|
||||
title: translate('login.compatibility_dialog.title'),
|
||||
description: translate('login.compatibility_dialog.body'));
|
||||
title: translate(
|
||||
'login.compatibility_dialog.title'),
|
||||
description: translate(
|
||||
'login.compatibility_dialog.body'));
|
||||
},
|
||||
),
|
||||
InkWell(
|
||||
child: Icon(
|
||||
model.useCredentialsLogin
|
||||
? Icons.person_outline
|
||||
: Icons.vpn_key,
|
||||
color: blueColor),
|
||||
onTap: () {
|
||||
model.toggleLoginMethod();
|
||||
},
|
||||
)
|
||||
])),
|
||||
LoginHeaders(
|
||||
validationMessage: model.errorMessage,
|
||||
uriController: model.uriController,
|
||||
usernameController: model.userNameController,
|
||||
passwordController: model.passwordController,
|
||||
),
|
||||
RaisedButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
padding: EdgeInsets.all(12),
|
||||
color: primaryAccentColor,
|
||||
child: Text(translate('login.button'), style: TextStyle(color: buttonForegroundColor)),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
model.useCredentialsLogin
|
||||
? LoginCredentialsHeaders(
|
||||
validationMessage: model.errorMessage,
|
||||
uriController: model.uriController,
|
||||
usernameController: model.userNameController,
|
||||
passwordController: model.passwordController,
|
||||
)
|
||||
: LoginApiKeyHeaders(
|
||||
validationMessage: model.errorMessage,
|
||||
uriController: model.uriController,
|
||||
apiKeyController: model.apiKeyController),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.login, color: blueColor),
|
||||
label: Text(translate('login.button')),
|
||||
onPressed: () async {
|
||||
var loginSuccess = await model.login(
|
||||
model.uriController.text, model.userNameController.text, model.passwordController.text);
|
||||
var loginSuccess = await model.login();
|
||||
if (loginSuccess) {
|
||||
_navigationService.navigateAndReplaceTo(HomeView.routeName);
|
||||
_navigationService
|
||||
.navigateAndReplaceTo(HomeView.routeName);
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
73
lib/ui/views/navbar_authenticated.dart
Normal file
73
lib/ui/views/navbar_authenticated.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
import 'package:fbmobile/core/util/logger.dart';
|
||||
import 'package:fbmobile/ui/views/profile_view.dart';
|
||||
import 'package:fbmobile/ui/views/upload_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import '../shared/app_colors.dart';
|
||||
import 'history_view.dart';
|
||||
|
||||
class AuthenticatedNavBarView extends StatefulWidget {
|
||||
const AuthenticatedNavBarView({super.key});
|
||||
|
||||
@override
|
||||
AuthenticatedNavBarState createState() => AuthenticatedNavBarState();
|
||||
}
|
||||
|
||||
class AuthenticatedNavBarState extends State<AuthenticatedNavBarView>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final Logger _logger = getLogger();
|
||||
int _currentTabIndex = 0;
|
||||
|
||||
void updateIndex(int targetIndex) {
|
||||
setState(() {
|
||||
_currentTabIndex = targetIndex;
|
||||
_logger.d("Changing current tab index to '$targetIndex'");
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: NavigationBar(
|
||||
key: UniqueKey(),
|
||||
onDestinationSelected: (int index) {
|
||||
updateIndex(index);
|
||||
},
|
||||
selectedIndex: _currentTabIndex,
|
||||
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
|
||||
destinations: const <Widget>[
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.upload_outlined),
|
||||
label: 'Upload',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.history_outlined),
|
||||
label: 'History',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.person_outlined),
|
||||
label: 'Profile',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: <Widget>[
|
||||
Container(
|
||||
color: myColor,
|
||||
alignment: Alignment.center,
|
||||
child: const UploadView(),
|
||||
),
|
||||
Container(
|
||||
color: myColor,
|
||||
alignment: Alignment.center,
|
||||
child: const HistoryView(),
|
||||
),
|
||||
Container(
|
||||
color: myColor,
|
||||
alignment: Alignment.center,
|
||||
child: const ProfileView(),
|
||||
),
|
||||
][_currentTabIndex],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../../core/enums/viewstate.dart';
|
||||
import '../../core/models/session.dart';
|
||||
import '../../core/util/formatter_util.dart';
|
||||
import '../../core/viewmodels/profile_model.dart';
|
||||
import '../shared/app_colors.dart';
|
||||
import '../shared/text_styles.dart';
|
||||
|
@ -16,73 +15,87 @@ import 'base_view.dart';
|
|||
class ProfileView extends StatelessWidget {
|
||||
static const routeName = '/profile';
|
||||
|
||||
const ProfileView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BaseView<ProfileModel>(
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(title: Text(translate('titles.profile'))),
|
||||
body: _render(model, context)));
|
||||
}
|
||||
|
||||
Widget _render(ProfileModel model, BuildContext context) {
|
||||
var url = Provider.of<Session>(context).url;
|
||||
var apiKey = Provider.of<Session>(context).apiKey;
|
||||
var config = Provider.of<Session>(context).config;
|
||||
|
||||
return BaseView<ProfileModel>(
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(title: Text(translate('titles.profile'))),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: "logoutButton",
|
||||
child: Icon(Icons.exit_to_app),
|
||||
backgroundColor: primaryAccentColor,
|
||||
onPressed: () {
|
||||
model.logout();
|
||||
},
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
body: model.state == ViewState.Busy
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: ListView(
|
||||
children: <Widget>[
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0),
|
||||
return model.state == ViewState.busy
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: ListView(
|
||||
children: <Widget>[
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
translate('profile.welcome'),
|
||||
style: headerStyle,
|
||||
translate('profile.instance'),
|
||||
style: subHeaderStyle,
|
||||
))),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0),
|
||||
child: Center(
|
||||
child: Linkify(
|
||||
onOpen: (link) => model.openLink(link.url),
|
||||
text: translate('profile.connection', args: {'url': url}),
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
))),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: ElevatedButton.icon(
|
||||
icon: model.configLoading
|
||||
? Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: const CircularProgressIndicator(
|
||||
color: blueColor,
|
||||
strokeWidth: 3,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.settings, color: blueColor),
|
||||
label: Text(
|
||||
model.configLoading
|
||||
? translate('profile.show_config_loading')
|
||||
: translate('profile.show_config'),
|
||||
),
|
||||
),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0),
|
||||
child: Linkify(
|
||||
onOpen: (link) => model.openLink(link.url),
|
||||
text: translate('profile.connection', args: {'url': url}),
|
||||
options: LinkifyOptions(humanize: false),
|
||||
)),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: RaisedButton.icon(
|
||||
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||
label: Text(
|
||||
translate('profile.reveal_api_key'),
|
||||
style: TextStyle(color: buttonForegroundColor),
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
color: primaryAccentColor,
|
||||
onPressed: () {
|
||||
return model.revealApiKey(apiKey);
|
||||
})),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0),
|
||||
child: Text(
|
||||
translate('profile.config', args: {
|
||||
'uploadMaxSize': FormatterUtil.formatBytes(config.uploadMaxSize, 2),
|
||||
'maxFilesPerRequest': config.maxFilesPerRequest,
|
||||
'maxInputVars': config.maxInputVars,
|
||||
'requestMaxSize': FormatterUtil.formatBytes(config.requestMaxSize, 2)
|
||||
}),
|
||||
)),
|
||||
],
|
||||
)),
|
||||
);
|
||||
onPressed: () async {
|
||||
await model.showConfig(url);
|
||||
})),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.lock, color: orangeColor),
|
||||
label: Text(
|
||||
translate('profile.reveal_api_key'),
|
||||
),
|
||||
onPressed: () {
|
||||
model.revealApiKey(apiKey);
|
||||
})),
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.exit_to_app, color: redColor),
|
||||
label: Text(
|
||||
translate('profile.logout'),
|
||||
),
|
||||
onPressed: () async {
|
||||
await model.logout();
|
||||
})),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider_architecture/provider_architecture.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
import '../../core/enums/viewstate.dart';
|
||||
import '../../core/viewmodels/startup_model.dart';
|
||||
|
@ -7,21 +7,24 @@ import '../../core/viewmodels/startup_model.dart';
|
|||
class StartUpView extends StatelessWidget {
|
||||
static const routeName = '/';
|
||||
|
||||
const StartUpView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelProvider<StartUpViewModel>.withConsumer(
|
||||
return ViewModelBuilder<StartUpViewModel>.reactive(
|
||||
viewModelBuilder: () => StartUpViewModel(),
|
||||
onModelReady: (model) => model.handleStartUpLogic(),
|
||||
onViewModelReady: (model) => model.handleStartUpLogic(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: model.state == ViewState.Busy
|
||||
body: model.state == ViewState.busy
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
(model.stateMessage.isNotEmpty ? Text(model.stateMessage) : Container())
|
||||
const CircularProgressIndicator(),
|
||||
(model.stateMessage!.isNotEmpty
|
||||
? Text(model.stateMessage!)
|
||||
: Container())
|
||||
]))
|
||||
: Container()));
|
||||
}
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../shared/app_colors.dart';
|
||||
import 'login_view.dart';
|
||||
|
||||
class AnonymousTabBarView extends StatefulWidget {
|
||||
@override
|
||||
AnonymousTabBarState createState() => AnonymousTabBarState();
|
||||
}
|
||||
|
||||
class AnonymousTabBarState extends State<AnonymousTabBarView> with SingleTickerProviderStateMixin {
|
||||
TabController _tabController;
|
||||
int _currentTabIndex = 0;
|
||||
|
||||
List<Widget> _realPages = [LoginView()];
|
||||
List<Widget> _tabPages = [LoginView()];
|
||||
List<bool> _hasInit = [true];
|
||||
|
||||
List<Widget> _tabsButton = [Tab(icon: Icon(Icons.person_outline), text: translate('tabs.login'))];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: _realPages.length, vsync: this)
|
||||
..addListener(() {
|
||||
int selectedIndex = _tabController.index;
|
||||
if (_currentTabIndex != selectedIndex) {
|
||||
if (!_hasInit[selectedIndex]) {
|
||||
_tabPages[selectedIndex] = _realPages[selectedIndex];
|
||||
_hasInit[selectedIndex] = true;
|
||||
}
|
||||
setState(() => _currentTabIndex = selectedIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: IndexedStack(index: _currentTabIndex, children: _tabPages),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: TabBar(
|
||||
labelColor: primaryAccentColor,
|
||||
tabs: _tabsButton,
|
||||
controller: _tabController,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../shared/app_colors.dart';
|
||||
import 'history_view.dart';
|
||||
import 'profile_view.dart';
|
||||
import 'upload_view.dart';
|
||||
|
||||
class AuthenticatedTabBarView extends StatefulWidget {
|
||||
@override
|
||||
AuthenticatedTabBarState createState() => AuthenticatedTabBarState();
|
||||
}
|
||||
|
||||
class AuthenticatedTabBarState extends State<AuthenticatedTabBarView> with SingleTickerProviderStateMixin {
|
||||
TabController _tabController;
|
||||
int _currentTabIndex = 0;
|
||||
|
||||
List<Widget> _realPages = [UploadView(), HistoryView(), ProfileView()];
|
||||
List<Widget> _tabPages = [
|
||||
UploadView(),
|
||||
Container(),
|
||||
Container(),
|
||||
];
|
||||
List<bool> _hasInit = [true, false, false];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: _realPages.length, vsync: this)
|
||||
..addListener(() {
|
||||
int selectedIndex = _tabController.index;
|
||||
if (_currentTabIndex != selectedIndex) {
|
||||
if (!_hasInit[selectedIndex]) {
|
||||
_tabPages[selectedIndex] = _realPages[selectedIndex];
|
||||
_hasInit[selectedIndex] = true;
|
||||
}
|
||||
setState(() => _currentTabIndex = selectedIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double width = MediaQuery.of(context).size.width;
|
||||
double yourWidth = width / 3;
|
||||
double yourHeight = 70;
|
||||
|
||||
List<Widget> _tabsButton = [
|
||||
Container(
|
||||
width: yourWidth,
|
||||
height: yourHeight,
|
||||
alignment: Alignment.center,
|
||||
child: Tab(icon: Icon(Icons.upload_rounded), text: translate('tabs.upload'))),
|
||||
Container(
|
||||
width: yourWidth,
|
||||
height: yourHeight,
|
||||
alignment: Alignment.center,
|
||||
child: Tab(icon: Icon(Icons.history), text: translate('tabs.history'))),
|
||||
Container(
|
||||
width: yourWidth,
|
||||
height: yourHeight,
|
||||
alignment: Alignment.center,
|
||||
child: Tab(icon: Icon(Icons.person), text: translate('tabs.profile'))),
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
body: IndexedStack(index: _currentTabIndex, children: _tabPages),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: TabBar(
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
labelColor: primaryAccentColor,
|
||||
labelPadding: EdgeInsets.all(0),
|
||||
tabs: _tabsButton,
|
||||
isScrollable: true,
|
||||
controller: _tabController,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,20 +1,23 @@
|
|||
import 'package:fbmobile/ui/views/login_view.dart';
|
||||
import 'package:fbmobile/ui/views/navbar_authenticated.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../core/models/session.dart';
|
||||
import 'tabbar_anonymous.dart';
|
||||
import 'tabbar_authenticated.dart';
|
||||
|
||||
class TabBarContainerView extends StatelessWidget {
|
||||
const TabBarContainerView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Session currentSession = Provider.of<Session>(context);
|
||||
bool isAuthenticated = currentSession != null && currentSession.apiKey.isNotEmpty;
|
||||
Session? currentSession = Provider.of<Session?>(context);
|
||||
bool isAuthenticated =
|
||||
currentSession != null ? currentSession.apiKey.isNotEmpty : false;
|
||||
|
||||
if (isAuthenticated) {
|
||||
return AuthenticatedTabBarView();
|
||||
return const AuthenticatedNavBarView();
|
||||
}
|
||||
|
||||
return AnonymousTabBarView();
|
||||
return LoginView();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:fbmobile/core/util/formatter_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -14,176 +15,223 @@ import 'base_view.dart';
|
|||
class UploadView extends StatelessWidget {
|
||||
static const routeName = '/upload';
|
||||
|
||||
const UploadView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var url = Provider.of<Session>(context).url;
|
||||
|
||||
return BaseView<UploadModel>(
|
||||
onModelReady: (model) => model.init(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(title: Text(translate('titles.upload'))),
|
||||
backgroundColor: backgroundColor,
|
||||
body: model.state == ViewState.Busy
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
(model.stateMessage != null && model.stateMessage.isNotEmpty
|
||||
? Text(model.stateMessage)
|
||||
: Container())
|
||||
]))
|
||||
: ListView(children: <Widget>[
|
||||
body: _render(model, context)));
|
||||
}
|
||||
|
||||
bool _isUploadButtonEnabled(UploadModel model) {
|
||||
return model.pasteTextTouched ||
|
||||
(model.paths != null && model.paths!.isNotEmpty);
|
||||
}
|
||||
|
||||
Widget _render(UploadModel model, BuildContext context) {
|
||||
var url = Provider.of<Session>(context).url;
|
||||
|
||||
return model.state == ViewState.busy
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
(model.stateMessage != null && model.stateMessage!.isNotEmpty
|
||||
? Text(model.stateMessage!)
|
||||
: Container())
|
||||
]))
|
||||
: ListView(children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: TextFormField(
|
||||
minLines: 1,
|
||||
maxLines: 7,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(
|
||||
Icons.text_snippet,
|
||||
color: buttonBackgroundColor,
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => model.pasteTextController.clear(),
|
||||
icon: Icon(Icons.clear),
|
||||
),
|
||||
hintText: translate('upload.text_to_be_pasted'),
|
||||
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
||||
),
|
||||
controller: model.pasteTextController)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
RaisedButton.icon(
|
||||
icon: Icon(Icons.file_copy_sharp, color: Colors.blue),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
color: primaryAccentColor,
|
||||
onPressed: () => model.openFileExplorer(),
|
||||
label: Text(
|
||||
translate('upload.open_file_explorer'),
|
||||
style: TextStyle(color: buttonForegroundColor),
|
||||
)),
|
||||
RaisedButton.icon(
|
||||
icon: Icon(Icons.cancel, color: Colors.orange),
|
||||
onPressed: model.paths != null && model.paths.length > 0
|
||||
? () => model.clearCachedFiles()
|
||||
: null,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
color: primaryAccentColor,
|
||||
label: Text(
|
||||
translate('upload.clear_temporary_files'),
|
||||
style: TextStyle(color: buttonForegroundColor),
|
||||
)),
|
||||
],
|
||||
)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
RaisedButton.icon(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
color: primaryAccentColor,
|
||||
onPressed: () async {
|
||||
List<String> items = await model.upload();
|
||||
|
||||
if (items != null) {
|
||||
var clipboardContent = '';
|
||||
items.forEach((element) {
|
||||
clipboardContent += '$url/$element\n';
|
||||
});
|
||||
|
||||
FlutterClipboard.copy(clipboardContent).then((value) {
|
||||
final snackBar = SnackBar(
|
||||
action: SnackBarAction(
|
||||
label: translate('upload.dismiss'),
|
||||
textColor: Colors.blue,
|
||||
onPressed: () {
|
||||
Scaffold.of(context).hideCurrentSnackBar();
|
||||
},
|
||||
),
|
||||
content: Text(translate('upload.uploaded')),
|
||||
duration: Duration(seconds: 10),
|
||||
);
|
||||
Scaffold.of(context).showSnackBar(snackBar);
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: Icon(Icons.upload_rounded, color: Colors.green),
|
||||
label: Text(
|
||||
translate('upload.upload'),
|
||||
style: TextStyle(color: buttonForegroundColor),
|
||||
)),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: model.createMulti,
|
||||
onChanged: (v) => model.toggleCreateMulti(),
|
||||
),
|
||||
Text(translate('upload.multipaste')),
|
||||
],
|
||||
)
|
||||
])),
|
||||
model.errorMessage != null && model.errorMessage.isNotEmpty
|
||||
? (Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: CenteredErrorRow(model.errorMessage)))
|
||||
: Container(),
|
||||
Builder(
|
||||
builder: (BuildContext context) => model.loadingPath
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: const CircularProgressIndicator(),
|
||||
)
|
||||
: model.paths != null
|
||||
? Container(
|
||||
padding: const EdgeInsets.only(bottom: 30.0),
|
||||
height: MediaQuery.of(context).size.height * 0.50,
|
||||
child: ListView.separated(
|
||||
itemCount:
|
||||
model.paths != null && model.paths.isNotEmpty ? model.paths.length : 1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final bool isMultiPath = model.paths != null && model.paths.isNotEmpty;
|
||||
final String name = (isMultiPath
|
||||
? model.paths.map((e) => e.name).toList()[index]
|
||||
: model.fileName ?? '...');
|
||||
final path = model.paths.length > 0
|
||||
? model.paths.map((e) => e.path).toList()[index].toString()
|
||||
: '';
|
||||
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
name,
|
||||
),
|
||||
subtitle: Text(path),
|
||||
));
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: TextFormField(
|
||||
minLines: 1,
|
||||
maxLines: 7,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(
|
||||
Icons.text_snippet,
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () =>
|
||||
model.pasteTextController.clear(),
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
hintText: translate('upload.text_to_be_pasted'),
|
||||
contentPadding: const EdgeInsets.fromLTRB(
|
||||
20.0, 10.0, 20.0, 10.0),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(32.0)),
|
||||
),
|
||||
controller: model.pasteTextController)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [Text(translate('upload.and_or'))])),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.file_copy_sharp,
|
||||
color: blueColor),
|
||||
onPressed: () => model.openFileExplorer(),
|
||||
label: Text(
|
||||
translate('upload.open_file_explorer'),
|
||||
)),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.cancel,
|
||||
color: orangeColor),
|
||||
onPressed: model.paths != null &&
|
||||
model.paths!.isNotEmpty
|
||||
? () => model.clearCachedFiles()
|
||||
: null,
|
||||
label: Text(
|
||||
translate('upload.clear_temporary_files'),
|
||||
)),
|
||||
],
|
||||
))
|
||||
])));
|
||||
)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: model.createMulti,
|
||||
onChanged: (v) => model.toggleCreateMulti(),
|
||||
),
|
||||
Text(translate('upload.multipaste')),
|
||||
])),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: !_isUploadButtonEnabled(model)
|
||||
? null
|
||||
: () async {
|
||||
Map<String, bool>? items =
|
||||
await model.upload();
|
||||
String? clipboardContent = model
|
||||
.generatePasteLinks(items, url);
|
||||
|
||||
if (clipboardContent != null &&
|
||||
clipboardContent.isNotEmpty) {
|
||||
FlutterClipboard.copy(
|
||||
clipboardContent)
|
||||
.then((value) {
|
||||
final snackBar = SnackBar(
|
||||
action: SnackBarAction(
|
||||
label: translate(
|
||||
'upload.dismiss'),
|
||||
textColor: blueColor,
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(
|
||||
context)
|
||||
.hideCurrentSnackBar();
|
||||
},
|
||||
),
|
||||
content: Text(translate(
|
||||
'upload.uploaded')),
|
||||
duration:
|
||||
const Duration(seconds: 10),
|
||||
);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(snackBar);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.upload_rounded,
|
||||
color: greenColor),
|
||||
label: Text(
|
||||
translate('upload.upload'),
|
||||
)),
|
||||
])),
|
||||
model.errorMessage != null && model.errorMessage!.isNotEmpty
|
||||
? (Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: CenteredErrorRow(model.errorMessage)))
|
||||
: Container(),
|
||||
Builder(
|
||||
builder: (BuildContext context) => model.loadingPath
|
||||
? const Padding(
|
||||
padding: EdgeInsets.only(bottom: 10.0),
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: model.paths != null
|
||||
? Container(
|
||||
padding: const EdgeInsets.only(bottom: 20.0),
|
||||
height:
|
||||
MediaQuery.of(context).size.height * 0.50,
|
||||
child: ListView.separated(
|
||||
itemCount: model.paths != null &&
|
||||
model.paths!.isNotEmpty
|
||||
? model.paths!.length
|
||||
: 1,
|
||||
itemBuilder:
|
||||
(BuildContext context, int index) {
|
||||
final bool isMultiPath =
|
||||
model.paths != null &&
|
||||
model.paths!.isNotEmpty;
|
||||
final String name = (isMultiPath
|
||||
? model.paths!
|
||||
.map((e) => e.name)
|
||||
.toList()[index]
|
||||
: model.fileName ?? '...');
|
||||
final size = model.paths!.isNotEmpty
|
||||
? model.paths!
|
||||
.map((e) => e.size)
|
||||
.toList()[index]
|
||||
.toString()
|
||||
: '';
|
||||
final path = model.paths!.isNotEmpty
|
||||
? model.paths!
|
||||
.map((e) => e.path)
|
||||
.toList()[index]
|
||||
.toString()
|
||||
: '';
|
||||
|
||||
return Card(
|
||||
child: ListTile(
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.clear,
|
||||
color: orangeColor),
|
||||
onPressed: () {
|
||||
model.deleteIntentFile(path);
|
||||
}),
|
||||
title: Text(
|
||||
"$name (${FormatterUtil.formatBytes(int.parse(size), 2)})",
|
||||
),
|
||||
subtitle: Text(path),
|
||||
));
|
||||
},
|
||||
separatorBuilder:
|
||||
(BuildContext context, int index) =>
|
||||
const Divider(),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
],
|
||||
))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,14 @@ import '../../locator.dart';
|
|||
import '../../ui/views/about_view.dart';
|
||||
|
||||
class AboutIconButton extends StatelessWidget {
|
||||
AboutIconButton();
|
||||
AboutIconButton({super.key});
|
||||
|
||||
final NavigationService _navigationService = locator<NavigationService>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: Icon(Icons.help),
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.help),
|
||||
onPressed: () {
|
||||
_navigationService.navigateTo(AboutView.routeName);
|
||||
});
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../shared/app_colors.dart';
|
||||
|
||||
class CenteredErrorRow extends StatelessWidget {
|
||||
final Function retryCallback;
|
||||
final String message;
|
||||
final Function? retryCallback;
|
||||
final String? message;
|
||||
|
||||
CenteredErrorRow(this.message, {this.retryCallback});
|
||||
const CenteredErrorRow(this.message, {super.key, this.retryCallback});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -21,7 +20,10 @@ class CenteredErrorRow extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(child: Center(child: Text(message, style: TextStyle(color: Colors.red)))),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(message!,
|
||||
style: const TextStyle(color: redColor)))),
|
||||
],
|
||||
),
|
||||
(retryCallback != null
|
||||
|
@ -31,10 +33,10 @@ class CenteredErrorRow extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Center(
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.refresh),
|
||||
icon: const Icon(Icons.refresh),
|
||||
color: primaryAccentColor,
|
||||
onPressed: () {
|
||||
retryCallback();
|
||||
retryCallback!();
|
||||
},
|
||||
))
|
||||
])
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
class LoginHeaders extends StatelessWidget {
|
||||
final TextEditingController uriController;
|
||||
final TextEditingController usernameController;
|
||||
final TextEditingController passwordController;
|
||||
|
||||
final String validationMessage;
|
||||
|
||||
LoginHeaders(
|
||||
{@required this.uriController,
|
||||
@required this.usernameController,
|
||||
@required this.passwordController,
|
||||
this.validationMessage});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: <Widget>[
|
||||
this.validationMessage != null ? Text(validationMessage, style: TextStyle(color: Colors.red)) : Container(),
|
||||
LoginTextField(uriController, translate('login.url_placeholder'), Icon(Icons.link),
|
||||
keyboardType: TextInputType.url),
|
||||
LoginTextField(usernameController, translate('login.username_placeholder'), Icon(Icons.person),
|
||||
keyboardType: TextInputType.name),
|
||||
LoginTextField(passwordController, translate('login.password_placeholder'), Icon(Icons.vpn_key),
|
||||
obscureText: true),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class LoginTextField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final String placeHolder;
|
||||
final TextInputType keyboardType;
|
||||
final bool obscureText;
|
||||
final Widget prefixIcon;
|
||||
|
||||
LoginTextField(this.controller, this.placeHolder, this.prefixIcon,
|
||||
{this.keyboardType = TextInputType.text, this.obscureText = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
margin: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
|
||||
height: 50.0,
|
||||
alignment: Alignment.centerLeft,
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10.0)),
|
||||
child: TextFormField(
|
||||
keyboardType: keyboardType,
|
||||
obscureText: obscureText,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => controller.clear(),
|
||||
icon: Icon(Icons.clear),
|
||||
),
|
||||
prefixIcon: prefixIcon,
|
||||
hintText: placeHolder,
|
||||
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
||||
),
|
||||
controller: controller),
|
||||
);
|
||||
}
|
||||
}
|
36
lib/ui/widgets/login_header_apikey.dart
Normal file
36
lib/ui/widgets/login_header_apikey.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../shared/app_colors.dart';
|
||||
import 'login_text_field.dart';
|
||||
|
||||
class LoginApiKeyHeaders extends StatelessWidget {
|
||||
final TextEditingController uriController;
|
||||
final TextEditingController apiKeyController;
|
||||
|
||||
final String? validationMessage;
|
||||
|
||||
const LoginApiKeyHeaders(
|
||||
{super.key,
|
||||
required this.uriController,
|
||||
required this.apiKeyController,
|
||||
this.validationMessage});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: <Widget>[
|
||||
validationMessage != null
|
||||
? Text(validationMessage!, style: const TextStyle(color: redColor))
|
||||
: Container(),
|
||||
LoginTextField(uriController, translate('login.url_placeholder'),
|
||||
const Icon(Icons.link),
|
||||
keyboardType: TextInputType.url),
|
||||
LoginTextField(
|
||||
apiKeyController,
|
||||
translate('login.apikey_placeholder'),
|
||||
const Icon(Icons.vpn_key),
|
||||
obscureText: true,
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
38
lib/ui/widgets/login_header_credentials.dart
Normal file
38
lib/ui/widgets/login_header_credentials.dart
Normal file
|
@ -0,0 +1,38 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../shared/app_colors.dart';
|
||||
import 'login_text_field.dart';
|
||||
|
||||
class LoginCredentialsHeaders extends StatelessWidget {
|
||||
final TextEditingController uriController;
|
||||
final TextEditingController usernameController;
|
||||
final TextEditingController passwordController;
|
||||
|
||||
final String? validationMessage;
|
||||
|
||||
const LoginCredentialsHeaders(
|
||||
{super.key,
|
||||
required this.uriController,
|
||||
required this.usernameController,
|
||||
required this.passwordController,
|
||||
this.validationMessage});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: <Widget>[
|
||||
validationMessage != null
|
||||
? Text(validationMessage!, style: const TextStyle(color: redColor))
|
||||
: Container(),
|
||||
LoginTextField(uriController, translate('login.url_placeholder'),
|
||||
const Icon(Icons.link),
|
||||
keyboardType: TextInputType.url),
|
||||
LoginTextField(usernameController,
|
||||
translate('login.username_placeholder'), const Icon(Icons.person),
|
||||
keyboardType: TextInputType.name),
|
||||
LoginTextField(passwordController,
|
||||
translate('login.password_placeholder'), const Icon(Icons.vpn_key),
|
||||
obscureText: true),
|
||||
]);
|
||||
}
|
||||
}
|
39
lib/ui/widgets/login_text_field.dart
Normal file
39
lib/ui/widgets/login_text_field.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoginTextField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final String placeHolder;
|
||||
final TextInputType keyboardType;
|
||||
final bool obscureText;
|
||||
final Widget prefixIcon;
|
||||
|
||||
const LoginTextField(this.controller, this.placeHolder, this.prefixIcon,
|
||||
{super.key,
|
||||
this.keyboardType = TextInputType.text,
|
||||
this.obscureText = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
|
||||
height: 50.0,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TextFormField(
|
||||
keyboardType: keyboardType,
|
||||
obscureText: obscureText,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => controller.clear(),
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
prefixIcon: prefixIcon,
|
||||
hintText: placeHolder,
|
||||
contentPadding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||
border:
|
||||
OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
||||
),
|
||||
controller: controller),
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue