Compare commits
No commits in common. "master" and "1.5.0+16" have entirely different histories.
89 changed files with 899 additions and 1717 deletions
14
.drone.yml
Normal file
14
.drone.yml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: cirrusci/flutter:3.3.9
|
||||||
|
commands:
|
||||||
|
- 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
|
|
@ -1,21 +0,0 @@
|
||||||
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
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,7 +1,6 @@
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
*.class
|
*.class
|
||||||
*.lock
|
*.lock
|
||||||
!Gemfile.lock
|
|
||||||
!pubspec.lock
|
!pubspec.lock
|
||||||
*.log
|
*.log
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -1,34 +1,5 @@
|
||||||
# CHANGELOG
|
# 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
|
## 1.5.0+16
|
||||||
* Switched to Material You defaulting to blue swatch colors respecting dark mode
|
* Switched to Material You defaulting to blue swatch colors respecting dark mode
|
||||||
* Switched to Material You navigation bar and removed unsupported swipe navigation
|
* Switched to Material You navigation bar and removed unsupported swipe navigation
|
||||||
|
|
|
@ -404,14 +404,6 @@ where to find the applicable terms.
|
||||||
form of a separately written license, or stated as exceptions;
|
form of a separately written license, or stated as exceptions;
|
||||||
the above requirements apply either way.
|
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.
|
8. Termination.
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
|
31
README.md
31
README.md
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
A mobile flutter app for [FileBin](https://git.server-speed.net/users/flo/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
|
Available on the [Play Store](https://play.google.com/store/apps/details?id=de.varakh.fbmobile).
|
||||||
[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)**.
|
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.
|
Other repositories are mirrors and pull requests, issues, and planning are managed there.
|
||||||
|
@ -95,7 +94,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.
|
You need access to the git repository in which those private files reside.
|
||||||
|
|
||||||
#### Usage / doing the actual release
|
#### Usage
|
||||||
|
|
||||||
Go into the platform directory you want to build for, e.g. `ios/` or `android/` and then look into the
|
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
|
`Fastlane` file which lanes are present. Run a lane via `fastlane <platform> <lane>`, e.g. use the
|
||||||
|
@ -105,37 +104,13 @@ following to build for Android `fastlane android build`.
|
||||||
|
|
||||||
##### Android
|
##### Android
|
||||||
|
|
||||||
It's recommended you set up `fastlane` via `bundler` (you need this to be installed on your machine).
|
Use `fastlane android beta` to build and upload a new beta version to the Play Store.
|
||||||
|
|
||||||
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
|
##### iOS
|
||||||
|
|
||||||
For iOS you need to execute `fastlane ios build` before uploading to testflight with
|
For iOS you need to execute `fastlane ios build` before uploading to testflight with
|
||||||
`fastlane ios beta`.
|
`fastlane ios beta`.
|
||||||
|
|
||||||
Probably do the same Ruby/fastlane setup as mentioned under the _Android_ section.
|
|
||||||
|
|
||||||
### Release manually (not recommended)
|
### Release manually (not recommended)
|
||||||
|
|
||||||
See the following links on how to setup:
|
See the following links on how to setup:
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
# 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,5 +5,3 @@ gradle-wrapper.jar
|
||||||
/gradlew.bat
|
/gradlew.bat
|
||||||
/local.properties
|
/local.properties
|
||||||
GeneratedPluginRegistrant.java
|
GeneratedPluginRegistrant.java
|
||||||
.bundle
|
|
||||||
vendor/
|
|
||||||
|
|
|
@ -1,222 +0,0 @@
|
||||||
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,9 +1,3 @@
|
||||||
plugins {
|
|
||||||
id "com.android.application"
|
|
||||||
id "kotlin-android"
|
|
||||||
id "dev.flutter.flutter-gradle-plugin"
|
|
||||||
}
|
|
||||||
|
|
||||||
def localProperties = new Properties()
|
def localProperties = new Properties()
|
||||||
def localPropertiesFile = rootProject.file('local.properties')
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
if (localPropertiesFile.exists()) {
|
if (localPropertiesFile.exists()) {
|
||||||
|
@ -12,6 +6,11 @@ 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')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
flutterVersionCode = '1'
|
flutterVersionCode = '1'
|
||||||
|
@ -22,6 +21,9 @@ if (flutterVersionName == null) {
|
||||||
flutterVersionName = '1.0'
|
flutterVersionName = '1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||||
if (keystorePropertiesFile.exists()) {
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
@ -29,9 +31,7 @@ if (keystorePropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 34
|
compileSdkVersion 33
|
||||||
|
|
||||||
namespace "de.varakh.fbmobile"
|
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'InvalidPackage'
|
disable 'InvalidPackage'
|
||||||
|
@ -39,8 +39,8 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.varakh.fbmobile"
|
applicationId "de.varakh.fbmobile"
|
||||||
minSdkVersion 30
|
minSdkVersion 16
|
||||||
targetSdkVersion 34
|
targetSdkVersion 33
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
|
@ -4,10 +4,7 @@
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<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_EXTERNAL_STORAGE" />
|
||||||
<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>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
|
|
|
@ -21,31 +21,36 @@
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="text/*" />
|
<data android:mimeType="text/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="image/*" />
|
<data android:mimeType="image/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="image/*" />
|
<data android:mimeType="image/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="image/*" />
|
<data android:mimeType="video/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="video/*" />
|
<data android:mimeType="video/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
@ -65,10 +70,7 @@
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<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_EXTERNAL_STORAGE" />
|
||||||
<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>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
|
|
|
@ -4,10 +4,7 @@
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<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_EXTERNAL_STORAGE" />
|
||||||
<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>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
@ -13,6 +24,6 @@ subprojects {
|
||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("clean", Delete) {
|
task clean(type: Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,6 @@ platform :android do
|
||||||
sh("#{ENV['PWD']}/fastlane/buildAndroidProduction.sh")
|
sh("#{ENV['PWD']}/fastlane/buildAndroidProduction.sh")
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Build Production fdroid"
|
|
||||||
lane :build_production_fdroid do
|
|
||||||
sh("#{ENV['PWD']}/fastlane/buildAndroidProductionFdroid.sh")
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Build"
|
desc "Build"
|
||||||
lane :build do
|
lane :build do
|
||||||
sh("#{ENV['PWD']}/fastlane/buildAndroid.sh")
|
sh("#{ENV['PWD']}/fastlane/buildAndroid.sh")
|
||||||
|
|
|
@ -31,14 +31,6 @@ Build Debug
|
||||||
|
|
||||||
Build Production
|
Build Production
|
||||||
|
|
||||||
### android build_production_fdroid
|
|
||||||
|
|
||||||
```sh
|
|
||||||
[bundle exec] fastlane android build_production_fdroid
|
|
||||||
```
|
|
||||||
|
|
||||||
Build Production fdroid
|
|
||||||
|
|
||||||
### android build
|
### android build
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
#!/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,6 +1,4 @@
|
||||||
agpVersion=8.7.2
|
|
||||||
kotlinVersion=1.7.10
|
|
||||||
|
|
||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.enableR8=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
|
#Fri Jun 23 08:50:38 CEST 2017
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
||||||
|
|
|
@ -1,25 +1,15 @@
|
||||||
pluginManagement {
|
include ':app'
|
||||||
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
|
|
||||||
}()
|
|
||||||
|
|
||||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
||||||
|
|
||||||
repositories {
|
def plugins = new Properties()
|
||||||
google()
|
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
||||||
mavenCentral()
|
if (pluginsFile.exists()) {
|
||||||
gradlePluginPortal()
|
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins.each { name, path ->
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
||||||
id "com.android.application" version "${agpVersion}" apply false
|
include ":$name"
|
||||||
id "org.jetbrains.kotlin.android" version "${kotlinVersion}" apply false
|
project(":$name").projectDir = pluginDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
|
||||||
|
|
|
@ -18,6 +18,12 @@
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"upload": "Upload"
|
"upload": "Upload"
|
||||||
},
|
},
|
||||||
|
"tabs": {
|
||||||
|
"login": "Login",
|
||||||
|
"history": "History",
|
||||||
|
"profile": "Profile",
|
||||||
|
"upload": "New"
|
||||||
|
},
|
||||||
"upload": {
|
"upload": {
|
||||||
"and_or": "and/or",
|
"and_or": "and/or",
|
||||||
"open_file_explorer": "Select file(s)...",
|
"open_file_explorer": "Select file(s)...",
|
||||||
|
@ -39,6 +45,7 @@
|
||||||
"start_services": "Starting services..."
|
"start_services": "Starting services..."
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
|
"help": "Login",
|
||||||
"compatibility_dialog": {
|
"compatibility_dialog": {
|
||||||
"title": "How to login?",
|
"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."
|
"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."
|
||||||
|
@ -126,6 +133,14 @@
|
||||||
"description": "Could not open '{link}'. Please ensure that you have an application installed which handles opening such link types."
|
"description": "Could not open '{link}'. Please ensure that you have an application installed which handles opening such link types."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"dialog": {
|
||||||
"confirm": "OK",
|
"confirm": "OK",
|
||||||
"cancel": "Cancel"
|
"cancel": "Cancel"
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
</dict>
|
</dict>
|
||||||
<dict/>
|
<dict/>
|
||||||
</array>
|
</array>
|
||||||
// TODO follow steps on create share extension (https://pub.dev/packages/flutter_sharing_intent)
|
// TODO follow steps 2) on create share extension (https://pub.dev/packages/receive_sharing_intent)
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
<string>Allow to select photos and upload them via the app</string>
|
<string>Allow to select photos and upload them via the app</string>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
|
29
lib/app.dart
29
lib/app.dart
|
@ -18,12 +18,10 @@ import 'ui/shared/app_colors.dart';
|
||||||
import 'ui/views/startup_view.dart';
|
import 'ui/views/startup_view.dart';
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
static final _defaultLightColorScheme = ColorScheme.fromSwatch(
|
static final _defaultLightColorScheme = ColorScheme.fromSwatch(primarySwatch: myColor, brightness: Brightness.light);
|
||||||
primarySwatch: myColor, brightness: Brightness.light);
|
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(primarySwatch: myColor, brightness: Brightness.dark);
|
||||||
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(
|
|
||||||
primarySwatch: myColor, brightness: Brightness.dark);
|
|
||||||
|
|
||||||
MyApp({super.key}) {
|
MyApp() {
|
||||||
initializeDateFormatting('en');
|
initializeDateFormatting('en');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,33 +33,26 @@ class MyApp extends StatelessWidget {
|
||||||
state: LocalizationProvider.of(context).state,
|
state: LocalizationProvider.of(context).state,
|
||||||
child: StreamProvider<RefreshEvent?>(
|
child: StreamProvider<RefreshEvent?>(
|
||||||
initialData: null,
|
initialData: null,
|
||||||
create: (context) =>
|
create: (context) => locator<RefreshService>().refreshEventController.stream,
|
||||||
locator<RefreshService>().refreshEventController.stream,
|
|
||||||
child: StreamProvider<Session?>(
|
child: StreamProvider<Session?>(
|
||||||
initialData: Session.initial(),
|
initialData: Session.initial(),
|
||||||
create: (context) =>
|
create: (context) => locator<SessionService>().sessionController.stream,
|
||||||
locator<SessionService>().sessionController.stream,
|
child: LifeCycleManager(child: DynamicColorBuilder(builder: (lightColorScheme, darkColorScheme) {
|
||||||
child: LifeCycleManager(child: DynamicColorBuilder(
|
|
||||||
builder: (lightColorScheme, darkColorScheme) {
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: translate('app.title'),
|
title: translate('app.title'),
|
||||||
builder: (context, child) => Navigator(
|
builder: (context, child) => Navigator(
|
||||||
key: locator<DialogService>().dialogNavigationKey,
|
key: locator<DialogService>().dialogNavigationKey,
|
||||||
onGenerateRoute: (settings) => MaterialPageRoute(
|
onGenerateRoute: (settings) => MaterialPageRoute(builder: (context) => DialogManager(child: child)),
|
||||||
builder: (context) => DialogManager(child: child)),
|
|
||||||
),
|
),
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
colorScheme:
|
colorScheme: lightColorScheme ?? _defaultLightColorScheme),
|
||||||
lightColorScheme ?? _defaultLightColorScheme),
|
darkTheme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme ?? _defaultDarkColorScheme),
|
||||||
darkTheme: ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
colorScheme: darkColorScheme ?? _defaultDarkColorScheme),
|
|
||||||
onGenerateRoute: AppRouter.generateRoute,
|
onGenerateRoute: AppRouter.generateRoute,
|
||||||
navigatorKey: locator<NavigationService>().navigationKey,
|
navigatorKey: locator<NavigationService>().navigationKey,
|
||||||
home: const StartUpView(),
|
home: StartUpView(),
|
||||||
supportedLocales: localizationDelegate.supportedLocales,
|
supportedLocales: localizationDelegate.supportedLocales,
|
||||||
locale: localizationDelegate.currentLocale,
|
locale: localizationDelegate.currentLocale,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
/// Enums for error codes
|
/// Enums for error codes
|
||||||
enum ErrorCode {
|
enum ErrorCode {
|
||||||
/// A generic error
|
/// A generic error
|
||||||
generalError,
|
GENERAL_ERROR,
|
||||||
|
|
||||||
/// Errors related to connections
|
/// Errors related to connections
|
||||||
socketError,
|
SOCKET_ERROR,
|
||||||
socketTimeout,
|
SOCKET_TIMEOUT,
|
||||||
|
|
||||||
/// A REST error (response code wasn't 200 or 204)
|
/// A REST error (response code wasn't 200 or 204)
|
||||||
restError,
|
REST_ERROR,
|
||||||
|
|
||||||
/// Custom errors
|
/// Custom errors
|
||||||
invalidApiKey
|
INVALID_API_KEY
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
enum RefreshEvent { refreshHistory }
|
enum RefreshEvent { RefreshHistory }
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
enum ViewState { idle, busy }
|
enum ViewState { Idle, Busy }
|
||||||
|
|
|
@ -5,10 +5,9 @@ class RestServiceException extends ServiceException {
|
||||||
final int statusCode;
|
final int statusCode;
|
||||||
final dynamic responseBody;
|
final dynamic responseBody;
|
||||||
|
|
||||||
RestServiceException(this.statusCode, {this.responseBody, super.message = null})
|
RestServiceException(this.statusCode, {this.responseBody, String? message})
|
||||||
: super(code: ErrorCode.restError);
|
: super(code: ErrorCode.REST_ERROR, message: message);
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
String toString() {
|
||||||
return "$code $statusCode $message";
|
return "$code $statusCode $message";
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,8 @@ class ServiceException implements Exception {
|
||||||
final ErrorCode code;
|
final ErrorCode code;
|
||||||
final String? message;
|
final String? message;
|
||||||
|
|
||||||
ServiceException({this.code = ErrorCode.generalError, this.message = ''});
|
ServiceException({this.code = ErrorCode.GENERAL_ERROR, this.message = ''});
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
String toString() {
|
||||||
return "$code: $message";
|
return "$code: $message";
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,13 @@ import '../services/dialog_service.dart';
|
||||||
class DialogManager extends StatefulWidget {
|
class DialogManager extends StatefulWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
|
|
||||||
const DialogManager({super.key, this.child});
|
DialogManager({Key? key, this.child}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
_DialogManagerState createState() => _DialogManagerState();
|
_DialogManagerState createState() => _DialogManagerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DialogManagerState extends State<DialogManager> {
|
class _DialogManagerState extends State<DialogManager> {
|
||||||
final DialogService _dialogService = locator<DialogService>();
|
DialogService _dialogService = locator<DialogService>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -31,8 +30,7 @@ class _DialogManagerState extends State<DialogManager> {
|
||||||
void _showDialog(DialogRequest request) {
|
void _showDialog(DialogRequest request) {
|
||||||
List<Widget> actions = <Widget>[];
|
List<Widget> actions = <Widget>[];
|
||||||
|
|
||||||
if (request.buttonTitleDeny != null &&
|
if (request.buttonTitleDeny != null && request.buttonTitleDeny!.isNotEmpty) {
|
||||||
request.buttonTitleDeny!.isNotEmpty) {
|
|
||||||
Widget denyBtn = TextButton(
|
Widget denyBtn = TextButton(
|
||||||
child: Text(request.buttonTitleDeny!),
|
child: Text(request.buttonTitleDeny!),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
|
@ -11,20 +11,15 @@ import '../util/logger.dart';
|
||||||
class LifeCycleManager extends StatefulWidget {
|
class LifeCycleManager extends StatefulWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
|
|
||||||
const LifeCycleManager({super.key, this.child});
|
LifeCycleManager({Key? key, this.child}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
_LifeCycleManagerState createState() => _LifeCycleManagerState();
|
_LifeCycleManagerState createState() => _LifeCycleManagerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LifeCycleManagerState extends State<LifeCycleManager>
|
class _LifeCycleManagerState extends State<LifeCycleManager> with WidgetsBindingObserver {
|
||||||
with WidgetsBindingObserver {
|
|
||||||
final Logger logger = getLogger();
|
final Logger logger = getLogger();
|
||||||
|
|
||||||
List<StoppableService> servicesToManage = [
|
List<StoppableService> servicesToManage = [locator<SessionService>(), locator<PermissionService>()];
|
||||||
locator<SessionService>(),
|
|
||||||
locator<PermissionService>()
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -48,12 +43,12 @@ class _LifeCycleManagerState extends State<LifeCycleManager>
|
||||||
logger.d('LifeCycle event ${state.toString()}');
|
logger.d('LifeCycle event ${state.toString()}');
|
||||||
super.didChangeAppLifecycleState(state);
|
super.didChangeAppLifecycleState(state);
|
||||||
|
|
||||||
for (var service in servicesToManage) {
|
servicesToManage.forEach((service) {
|
||||||
if (state == AppLifecycleState.resumed) {
|
if (state == AppLifecycleState.resumed) {
|
||||||
service.start();
|
service.start();
|
||||||
} else {
|
} else {
|
||||||
service.stop();
|
service.stop();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,7 @@ class ApiKey {
|
||||||
|
|
||||||
final String? comment;
|
final String? comment;
|
||||||
|
|
||||||
ApiKey(
|
ApiKey({required this.key, required this.created, required this.accessLevel, this.comment});
|
||||||
{required this.key,
|
|
||||||
required this.created,
|
|
||||||
required this.accessLevel,
|
|
||||||
this.comment});
|
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory ApiKey.fromJson(Map<String, dynamic> json) => _$ApiKeyFromJson(json);
|
factory ApiKey.fromJson(Map<String, dynamic> json) => _$ApiKeyFromJson(json);
|
||||||
|
|
|
@ -12,8 +12,7 @@ class ApiKeys {
|
||||||
ApiKeys({required this.apikeys});
|
ApiKeys({required this.apikeys});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory ApiKeys.fromJson(Map<String, dynamic> json) =>
|
factory ApiKeys.fromJson(Map<String, dynamic> json) => _$ApiKeysFromJson(json);
|
||||||
_$ApiKeysFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$ApiKeysToJson(this);
|
Map<String, dynamic> toJson() => _$ApiKeysToJson(this);
|
||||||
|
|
|
@ -15,8 +15,7 @@ class ApiKeysResponse {
|
||||||
ApiKeysResponse({required this.status, required this.data});
|
ApiKeysResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory ApiKeysResponse.fromJson(Map<String, dynamic> json) =>
|
factory ApiKeysResponse.fromJson(Map<String, dynamic> json) => _$ApiKeysResponseFromJson(json);
|
||||||
_$ApiKeysResponseFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$ApiKeysResponseToJson(this);
|
Map<String, dynamic> toJson() => _$ApiKeysResponseToJson(this);
|
||||||
|
|
|
@ -15,8 +15,7 @@ class ConfigResponse {
|
||||||
ConfigResponse({required this.status, required this.data});
|
ConfigResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory ConfigResponse.fromJson(Map<String, dynamic> json) =>
|
factory ConfigResponse.fromJson(Map<String, dynamic> json) => _$ConfigResponseFromJson(json);
|
||||||
_$ConfigResponseFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$ConfigResponseToJson(this);
|
Map<String, dynamic> toJson() => _$ConfigResponseToJson(this);
|
||||||
|
|
|
@ -13,8 +13,7 @@ class CreateApiKeyResponse {
|
||||||
CreateApiKeyResponse({required this.status, required this.data});
|
CreateApiKeyResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory CreateApiKeyResponse.fromJson(Map<String, dynamic> json) =>
|
factory CreateApiKeyResponse.fromJson(Map<String, dynamic> json) => _$CreateApiKeyResponseFromJson(json);
|
||||||
_$CreateApiKeyResponseFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$CreateApiKeyResponseToJson(this);
|
Map<String, dynamic> toJson() => _$CreateApiKeyResponseToJson(this);
|
||||||
|
|
|
@ -19,8 +19,7 @@ class History {
|
||||||
History({required this.items, required this.multipasteItems, this.totalSize});
|
History({required this.items, required this.multipasteItems, this.totalSize});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory History.fromJson(Map<String, dynamic> json) =>
|
factory History.fromJson(Map<String, dynamic> json) => _$HistoryFromJson(json);
|
||||||
_$HistoryFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$HistoryToJson(this);
|
Map<String, dynamic> toJson() => _$HistoryToJson(this);
|
||||||
|
|
|
@ -23,8 +23,7 @@ class HistoryItem {
|
||||||
this.thumbnail});
|
this.thumbnail});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory HistoryItem.fromJson(Map<String, dynamic> json) =>
|
factory HistoryItem.fromJson(Map<String, dynamic> json) => _$HistoryItemFromJson(json);
|
||||||
_$HistoryItemFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$HistoryItemToJson(this);
|
Map<String, dynamic> toJson() => _$HistoryItemToJson(this);
|
||||||
|
|
|
@ -15,8 +15,7 @@ class HistoryMultipasteItem {
|
||||||
HistoryMultipasteItem(this.items, {required this.date, required this.urlId});
|
HistoryMultipasteItem(this.items, {required this.date, required this.urlId});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory HistoryMultipasteItem.fromJson(Map<String, dynamic> json) =>
|
factory HistoryMultipasteItem.fromJson(Map<String, dynamic> json) => _$HistoryMultipasteItemFromJson(json);
|
||||||
_$HistoryMultipasteItemFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$HistoryMultipasteItemToJson(this);
|
Map<String, dynamic> toJson() => _$HistoryMultipasteItemToJson(this);
|
||||||
|
|
|
@ -9,8 +9,7 @@ class HistoryMultipasteItemEntry {
|
||||||
HistoryMultipasteItemEntry({required this.id});
|
HistoryMultipasteItemEntry({required this.id});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory HistoryMultipasteItemEntry.fromJson(Map<String, dynamic> json) =>
|
factory HistoryMultipasteItemEntry.fromJson(Map<String, dynamic> json) => _$HistoryMultipasteItemEntryFromJson(json);
|
||||||
_$HistoryMultipasteItemEntryFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$HistoryMultipasteItemEntryToJson(this);
|
Map<String, dynamic> toJson() => _$HistoryMultipasteItemEntryToJson(this);
|
||||||
|
|
|
@ -15,8 +15,7 @@ class HistoryResponse {
|
||||||
HistoryResponse({required this.status, required this.data});
|
HistoryResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory HistoryResponse.fromJson(Map<String, dynamic> json) =>
|
factory HistoryResponse.fromJson(Map<String, dynamic> json) => _$HistoryResponseFromJson(json);
|
||||||
_$HistoryResponseFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$HistoryResponseToJson(this);
|
Map<String, dynamic> toJson() => _$HistoryResponseToJson(this);
|
||||||
|
|
|
@ -15,8 +15,7 @@ class RestError {
|
||||||
required this.errorId,
|
required this.errorId,
|
||||||
}); // JSON Init
|
}); // JSON Init
|
||||||
|
|
||||||
factory RestError.fromJson(Map<String, dynamic> json) =>
|
factory RestError.fromJson(Map<String, dynamic> json) => _$RestErrorFromJson(json);
|
||||||
_$RestErrorFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$RestErrorToJson(this);
|
Map<String, dynamic> toJson() => _$RestErrorToJson(this);
|
||||||
|
|
|
@ -13,8 +13,7 @@ class Uploaded {
|
||||||
Uploaded({required this.ids, required this.urls});
|
Uploaded({required this.ids, required this.urls});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory Uploaded.fromJson(Map<String, dynamic> json) =>
|
factory Uploaded.fromJson(Map<String, dynamic> json) => _$UploadedFromJson(json);
|
||||||
_$UploadedFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedToJson(this);
|
||||||
|
|
|
@ -13,8 +13,7 @@ class UploadedMulti {
|
||||||
UploadedMulti({required this.url, required this.urlId});
|
UploadedMulti({required this.url, required this.urlId});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory UploadedMulti.fromJson(Map<String, dynamic> json) =>
|
factory UploadedMulti.fromJson(Map<String, dynamic> json) => _$UploadedMultiFromJson(json);
|
||||||
_$UploadedMultiFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedMultiToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedMultiToJson(this);
|
||||||
|
|
|
@ -15,8 +15,7 @@ class UploadedMultiResponse {
|
||||||
UploadedMultiResponse({required this.status, required this.data});
|
UploadedMultiResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory UploadedMultiResponse.fromJson(Map<String, dynamic> json) =>
|
factory UploadedMultiResponse.fromJson(Map<String, dynamic> json) => _$UploadedMultiResponseFromJson(json);
|
||||||
_$UploadedMultiResponseFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedMultiResponseToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedMultiResponseToJson(this);
|
||||||
|
|
|
@ -15,8 +15,7 @@ class UploadedResponse {
|
||||||
UploadedResponse({required this.status, required this.data});
|
UploadedResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory UploadedResponse.fromJson(Map<String, dynamic> json) =>
|
factory UploadedResponse.fromJson(Map<String, dynamic> json) => _$UploadedResponseFromJson(json);
|
||||||
_$UploadedResponseFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedResponseToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedResponseToJson(this);
|
||||||
|
|
|
@ -13,8 +13,7 @@ class Session {
|
||||||
: url = '',
|
: url = '',
|
||||||
apiKey = '';
|
apiKey = '';
|
||||||
|
|
||||||
factory Session.fromJson(Map<String, dynamic> json) =>
|
factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json);
|
||||||
_$SessionFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$SessionToJson(this);
|
Map<String, dynamic> toJson() => _$SessionToJson(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,7 @@ class UploadedPaste {
|
||||||
this.items});
|
this.items});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory UploadedPaste.fromJson(Map<String, dynamic> json) =>
|
factory UploadedPaste.fromJson(Map<String, dynamic> json) => _$UploadedPasteFromJson(json);
|
||||||
_$UploadedPasteFromJson(json);
|
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedPasteToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedPasteToJson(this);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import '../models/rest/uploaded_response.dart';
|
||||||
import '../services/api.dart';
|
import '../services/api.dart';
|
||||||
|
|
||||||
class FileRepository {
|
class FileRepository {
|
||||||
final Api _api = locator<Api>();
|
Api _api = locator<Api>();
|
||||||
|
|
||||||
Future<History> getHistory() async {
|
Future<History> getHistory() async {
|
||||||
var response = await _api.post('/file/history');
|
var response = await _api.post('/file/history');
|
||||||
|
@ -33,23 +33,19 @@ class FileRepository {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UploadedResponse> postUpload(
|
Future<UploadedResponse> postUpload(List<File>? files, Map<String, String>? additionalFiles) async {
|
||||||
List<File>? files, Map<String, String>? additionalFiles) async {
|
var response = await _api.post('/file/upload', files: files, additionalFiles: additionalFiles);
|
||||||
var response = await _api.post('/file/upload',
|
|
||||||
files: files, additionalFiles: additionalFiles);
|
|
||||||
return UploadedResponse.fromJson(json.decode(response.body));
|
return UploadedResponse.fromJson(json.decode(response.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UploadedMultiResponse> postCreateMultiPaste(List<String> ids) async {
|
Future<UploadedMultiResponse> postCreateMultiPaste(List<String> ids) async {
|
||||||
Map<String, String> multiPasteIds = {};
|
Map<String, String> multiPasteIds = Map();
|
||||||
|
|
||||||
for (var element in ids) {
|
ids.forEach((element) {
|
||||||
multiPasteIds.putIfAbsent(
|
multiPasteIds.putIfAbsent("ids[${ids.indexOf(element) + 1}]", () => element);
|
||||||
"ids[${ids.indexOf(element) + 1}]", () => element);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
var response =
|
var response = await _api.post('/file/create_multipaste', fields: multiPasteIds);
|
||||||
await _api.post('/file/create_multipaste', fields: multiPasteIds);
|
|
||||||
return UploadedMultiResponse.fromJson(json.decode(response.body));
|
return UploadedMultiResponse.fromJson(json.decode(response.body));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ import '../models/rest/create_apikey_response.dart';
|
||||||
import '../services/api.dart';
|
import '../services/api.dart';
|
||||||
|
|
||||||
class UserRepository {
|
class UserRepository {
|
||||||
final Api _api = locator<Api>();
|
Api _api = locator<Api>();
|
||||||
|
|
||||||
Future<CreateApiKeyResponse> postApiKey(String url, String username,
|
Future<CreateApiKeyResponse> postApiKey(
|
||||||
String password, String accessLevel, String comment) async {
|
String url, String username, String password, String accessLevel, String comment) async {
|
||||||
_api.setUrl(url);
|
_api.setUrl(url);
|
||||||
|
|
||||||
var fields = Map.fromEntries([
|
var fields = Map.fromEntries([
|
||||||
|
|
|
@ -24,34 +24,25 @@ class Api implements ApiErrorConverter {
|
||||||
String _url = "";
|
String _url = "";
|
||||||
String _apiKey = "";
|
String _apiKey = "";
|
||||||
|
|
||||||
final Map<String, String> _headers = {
|
Map<String, String> _headers = {"Content-Type": _applicationJson, "Accept": _applicationJson};
|
||||||
"Content-Type": _applicationJson,
|
Duration _timeout = Duration(seconds: Constants.apiRequestTimeoutLimit);
|
||||||
"Accept": _applicationJson
|
|
||||||
};
|
|
||||||
Duration _timeout = const Duration(seconds: Constants.apiRequestTimeoutLimit);
|
|
||||||
|
|
||||||
Future<http.Response> fetch<T>(String route) async {
|
Future<http.Response> fetch<T>(String route) async {
|
||||||
try {
|
try {
|
||||||
_logger.d(
|
_logger
|
||||||
"Requesting GET API endpoint '${_url + route}' with headers '$_headers' and maximum timeout '$_timeout'");
|
.d("Requesting GET API endpoint '${_url + route}' with headers '$_headers' and maximum timeout '$_timeout'");
|
||||||
var response = await http
|
var response = await http.get(Uri.parse(_url + route), headers: _headers).timeout(_timeout);
|
||||||
.get(Uri.parse(_url + route), headers: _headers)
|
|
||||||
.timeout(_timeout);
|
|
||||||
handleRestErrors(response);
|
handleRestErrors(response);
|
||||||
return response;
|
return response;
|
||||||
} on TimeoutException {
|
} on TimeoutException {
|
||||||
throw ServiceException(
|
throw ServiceException(code: ErrorCode.SOCKET_TIMEOUT, message: _errorTimeout);
|
||||||
code: ErrorCode.socketTimeout, message: _errorTimeout);
|
|
||||||
} on SocketException {
|
} on SocketException {
|
||||||
throw ServiceException(
|
throw ServiceException(code: ErrorCode.SOCKET_ERROR, message: _errorNoConnection);
|
||||||
code: ErrorCode.socketError, message: _errorNoConnection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<http.Response> post<T>(String route,
|
Future<http.Response> post<T>(String route,
|
||||||
{Map<String, String?>? fields,
|
{Map<String, String?>? fields, List<File>? files, Map<String, String>? additionalFiles}) async {
|
||||||
List<File>? files,
|
|
||||||
Map<String, String>? additionalFiles}) async {
|
|
||||||
try {
|
try {
|
||||||
var uri = Uri.parse(_url + route);
|
var uri = Uri.parse(_url + route);
|
||||||
var request = http.MultipartRequest('POST', uri)
|
var request = http.MultipartRequest('POST', uri)
|
||||||
|
@ -67,35 +58,28 @@ class Api implements ApiErrorConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files != null && files.isNotEmpty) {
|
if (files != null && files.isNotEmpty) {
|
||||||
for (var element in files) {
|
files.forEach((element) async {
|
||||||
request.files.add(await http.MultipartFile.fromPath(
|
request.files.add(await http.MultipartFile.fromPath('file[${files.indexOf(element) + 1}]', element.path));
|
||||||
'file[${files.indexOf(element) + 1}]', element.path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.d(
|
if (additionalFiles != null && additionalFiles.length > 0) {
|
||||||
"Requesting POST API endpoint '${uri.toString()}' and ${request.files.length} files");
|
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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.d("Requesting POST API endpoint '${uri.toString()}' and ${request.files.length} files");
|
||||||
var multiResponse = await request.send();
|
var multiResponse = await request.send();
|
||||||
var response = await http.Response.fromStream(multiResponse);
|
var response = await http.Response.fromStream(multiResponse);
|
||||||
handleRestErrors(response);
|
handleRestErrors(response);
|
||||||
return response;
|
return response;
|
||||||
} on TimeoutException {
|
} on TimeoutException {
|
||||||
throw ServiceException(
|
throw ServiceException(code: ErrorCode.SOCKET_TIMEOUT, message: _errorTimeout);
|
||||||
code: ErrorCode.socketTimeout, message: _errorTimeout);
|
|
||||||
} on SocketException {
|
} on SocketException {
|
||||||
throw ServiceException(
|
throw ServiceException(code: ErrorCode.SOCKET_ERROR, message: _errorNoConnection);
|
||||||
code: ErrorCode.socketError, message: _errorNoConnection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,21 +107,18 @@ class Api implements ApiErrorConverter {
|
||||||
/// have a json decoded object. Replace this with a custom
|
/// have a json decoded object. Replace this with a custom
|
||||||
/// conversion method by overwriting the interface if needed
|
/// conversion method by overwriting the interface if needed
|
||||||
void handleRestErrors(http.Response response) {
|
void handleRestErrors(http.Response response) {
|
||||||
if (response.statusCode != HttpStatus.ok &&
|
if (response.statusCode != HttpStatus.ok && response.statusCode != HttpStatus.noContent) {
|
||||||
response.statusCode != HttpStatus.noContent) {
|
|
||||||
if (response.headers.containsKey(HttpHeaders.contentTypeHeader)) {
|
if (response.headers.containsKey(HttpHeaders.contentTypeHeader)) {
|
||||||
ContentType responseContentType =
|
ContentType responseContentType = ContentType.parse(response.headers[HttpHeaders.contentTypeHeader]!);
|
||||||
ContentType.parse(response.headers[HttpHeaders.contentTypeHeader]!);
|
|
||||||
|
|
||||||
if (ContentType.json.primaryType == responseContentType.primaryType &&
|
if (ContentType.json.primaryType == responseContentType.primaryType &&
|
||||||
ContentType.json.subType == responseContentType.subType) {
|
ContentType.json.subType == responseContentType.subType) {
|
||||||
var parsedBody = convert(response);
|
var parsedBody = convert(response);
|
||||||
throw RestServiceException(response.statusCode,
|
throw new RestServiceException(response.statusCode, responseBody: parsedBody);
|
||||||
responseBody: parsedBody);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw RestServiceException(response.statusCode);
|
throw new RestServiceException(response.statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ import '../datamodels/dialog_request.dart';
|
||||||
import '../datamodels/dialog_response.dart';
|
import '../datamodels/dialog_response.dart';
|
||||||
|
|
||||||
class DialogService {
|
class DialogService {
|
||||||
final GlobalKey<NavigatorState> _dialogNavigationKey =
|
GlobalKey<NavigatorState> _dialogNavigationKey = GlobalKey<NavigatorState>();
|
||||||
GlobalKey<NavigatorState>();
|
|
||||||
late Function(DialogRequest) _showDialogListener;
|
late Function(DialogRequest) _showDialogListener;
|
||||||
Completer<DialogResponse>? _dialogCompleter;
|
Completer<DialogResponse>? _dialogCompleter;
|
||||||
|
|
||||||
|
@ -28,28 +27,20 @@ class DialogService {
|
||||||
title: title,
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
buttonTitleAccept:
|
buttonTitleAccept:
|
||||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty
|
buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept));
|
||||||
? translate('dialog.confirm')
|
|
||||||
: buttonTitleAccept));
|
|
||||||
return _dialogCompleter!.future;
|
return _dialogCompleter!.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DialogResponse> showConfirmationDialog(
|
Future<DialogResponse> showConfirmationDialog(
|
||||||
{String? title,
|
{String? title, String? description, String? buttonTitleAccept, String? buttonTitleDeny}) {
|
||||||
String? description,
|
|
||||||
String? buttonTitleAccept,
|
|
||||||
String? buttonTitleDeny}) {
|
|
||||||
_dialogCompleter = Completer<DialogResponse>();
|
_dialogCompleter = Completer<DialogResponse>();
|
||||||
_showDialogListener(DialogRequest(
|
_showDialogListener(DialogRequest(
|
||||||
title: title,
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
buttonTitleAccept:
|
buttonTitleAccept:
|
||||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty
|
buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept,
|
||||||
? translate('dialog.confirm')
|
buttonTitleDeny:
|
||||||
: buttonTitleAccept,
|
buttonTitleDeny == null || buttonTitleDeny.isEmpty ? translate('dialog.cancel') : buttonTitleDeny));
|
||||||
buttonTitleDeny: buttonTitleDeny == null || buttonTitleDeny.isEmpty
|
|
||||||
? translate('dialog.cancel')
|
|
||||||
: buttonTitleDeny));
|
|
||||||
return _dialogCompleter!.future;
|
return _dialogCompleter!.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,7 @@ class FileService {
|
||||||
return await _fileRepository.postDelete(id);
|
return await _fileRepository.postDelete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UploadedResponse> uploadPaste(
|
Future<UploadedResponse> uploadPaste(List<File>? files, Map<String, String>? additionalFiles) async {
|
||||||
List<File>? files, Map<String, String>? additionalFiles) async {
|
|
||||||
return await _fileRepository.postUpload(files, additionalFiles);
|
return await _fileRepository.postUpload(files, additionalFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,7 @@ class LinkService {
|
||||||
_logger.e('Could not launch link $link');
|
_logger.e('Could not launch link $link');
|
||||||
_dialogService.showDialog(
|
_dialogService.showDialog(
|
||||||
title: translate('link.dialog.title'),
|
title: translate('link.dialog.title'),
|
||||||
description:
|
description: translate('link.dialog.description', args: {'link': link}));
|
||||||
translate('link.dialog.description', args: {'link': link}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:logger/logger.dart';
|
||||||
import '../util/logger.dart';
|
import '../util/logger.dart';
|
||||||
|
|
||||||
class NavigationService {
|
class NavigationService {
|
||||||
final GlobalKey<NavigatorState> _navigationKey = GlobalKey<NavigatorState>();
|
GlobalKey<NavigatorState> _navigationKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
GlobalKey<NavigatorState> get navigationKey => _navigationKey;
|
GlobalKey<NavigatorState> get navigationKey => _navigationKey;
|
||||||
|
|
||||||
|
@ -17,13 +17,11 @@ class NavigationService {
|
||||||
|
|
||||||
Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
|
Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
|
||||||
logger.d('NavigationService: navigateTo $routeName');
|
logger.d('NavigationService: navigateTo $routeName');
|
||||||
return _navigationKey.currentState!
|
return _navigationKey.currentState!.pushNamed(routeName, arguments: arguments);
|
||||||
.pushNamed(routeName, arguments: arguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateAndReplaceTo(String routeName, {dynamic arguments}) {
|
Future<dynamic> navigateAndReplaceTo(String routeName, {dynamic arguments}) {
|
||||||
logger.d('NavigationService: navigateAndReplaceTo $routeName');
|
logger.d('NavigationService: navigateAndReplaceTo $routeName');
|
||||||
return _navigationKey.currentState!
|
return _navigationKey.currentState!.pushReplacementNamed(routeName, arguments: arguments);
|
||||||
.pushReplacementNamed(routeName, arguments: arguments);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +1,115 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io' show Platform;
|
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
import '../../constants.dart';
|
import '../../constants.dart';
|
||||||
|
import '../../core/datamodels/dialog_response.dart';
|
||||||
|
import '../../core/services/dialog_service.dart';
|
||||||
import '../../core/services/stoppable_service.dart';
|
import '../../core/services/stoppable_service.dart';
|
||||||
import '../../core/util/logger.dart';
|
import '../../core/util/logger.dart';
|
||||||
|
import '../../locator.dart';
|
||||||
|
import 'storage_service.dart';
|
||||||
|
|
||||||
class PermissionService extends StoppableService {
|
class PermissionService extends StoppableService {
|
||||||
final Logger _logger = getLogger();
|
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 _devicePermissionDialogActive = false;
|
||||||
|
bool _ownPermissionDialogActive = false;
|
||||||
|
|
||||||
bool _deviceInformationInitialized = false;
|
PermissionService() {
|
||||||
bool _useStoragePermission = true;
|
_devicePermissionDialogActive = true;
|
||||||
|
|
||||||
PermissionService();
|
Permission.storage.request().then((status) {
|
||||||
|
_permissionStatus = status;
|
||||||
|
if (PermissionStatus.permanentlyDenied == status) {
|
||||||
|
_permanentlyIgnored = true;
|
||||||
|
}
|
||||||
|
}).whenComplete(() {
|
||||||
|
_logger.d('Initial device request permission finished');
|
||||||
|
_devicePermissionDialogActive = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future checkEnabledAndPermission() async {
|
Future checkEnabledAndPermission() async {
|
||||||
|
if (_permanentlyIgnored) {
|
||||||
|
await _storageService.storeStoragePermissionDialogIgnored();
|
||||||
|
_permanentlyIgnored = false;
|
||||||
|
_logger.d('Set permanently ignored permission request');
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
if (_devicePermissionDialogActive) {
|
if (_devicePermissionDialogActive) {
|
||||||
_logger.d('Device permission dialog active, skipping');
|
_logger.d('Device permission dialog active, skipping');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool allGranted = false;
|
if (_ownPermissionDialogActive) {
|
||||||
bool anyPermanentlyDenied = false;
|
_logger.d('Own permission dialog already active, skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Since Android compileSdk >= 33, "storage" is deprecated
|
var ignoredDialog = await _storageService.hasStoragePermissionDialogIgnored();
|
||||||
// Instead, request access to all of
|
|
||||||
// - Permission.photos
|
if (ignoredDialog) {
|
||||||
// - Permission.videos
|
_logger.d('Permanently ignored permission request, skipping');
|
||||||
// - Permission.audio
|
stop();
|
||||||
//
|
return;
|
||||||
// For iOS and Android < 33, keep using "storage"
|
}
|
||||||
if (_useStoragePermission) {
|
|
||||||
PermissionStatus storagePermission = await Permission.storage.status;
|
_permissionStatus = await Permission.storage.status;
|
||||||
allGranted = PermissionStatus.granted == storagePermission;
|
if (_permissionStatus != PermissionStatus.granted) {
|
||||||
anyPermanentlyDenied =
|
if (_permissionStatus == PermissionStatus.permanentlyDenied) {
|
||||||
PermissionStatus.permanentlyDenied == storagePermission;
|
await _storageService.storeStoragePermissionDialogIgnored();
|
||||||
|
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'));
|
||||||
|
|
||||||
|
if (!response.confirmed!) {
|
||||||
|
await _storageService.storeStoragePermissionDialogIgnored();
|
||||||
} else {
|
} 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// all good, stop the permission service
|
|
||||||
if (allGranted) {
|
|
||||||
_logger.d("All permissions have been granted, stopping service");
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not all have been granted, show OS dialog
|
|
||||||
_logger.d(
|
|
||||||
"Not all permissions have been granted yet, initializing permission dialog");
|
|
||||||
_devicePermissionDialogActive = true;
|
_devicePermissionDialogActive = true;
|
||||||
|
Permission.storage.request().then((status) async {
|
||||||
|
if (PermissionStatus.permanentlyDenied == status) {
|
||||||
|
await _storageService.storeStoragePermissionDialogIgnored();
|
||||||
|
}
|
||||||
|
}).whenComplete(() {
|
||||||
|
_logger.d('Device request permission finished');
|
||||||
|
_devicePermissionDialogActive = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (_useStoragePermission) {
|
_ownPermissionDialogActive = false;
|
||||||
await [Permission.storage].request().whenComplete(() {
|
|
||||||
_logger.d('Device request permission finished');
|
|
||||||
_devicePermissionDialogActive = false;
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await [Permission.photos, Permission.videos, Permission.audio]
|
await _storageService.storeStoragePermissionDialogIgnored();
|
||||||
.request()
|
|
||||||
.whenComplete(() {
|
|
||||||
_logger.d('Device request permission finished');
|
|
||||||
_devicePermissionDialogActive = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future start() async {
|
Future start() async {
|
||||||
super.start();
|
super.start();
|
||||||
await _determineDeviceInfo();
|
|
||||||
await checkEnabledAndPermission();
|
await checkEnabledAndPermission();
|
||||||
|
|
||||||
_serviceCheckTimer = Timer.periodic(
|
_serviceCheckTimer =
|
||||||
const Duration(milliseconds: Constants.mediaPermissionCheckInterval),
|
Timer.periodic(Duration(milliseconds: Constants.mediaPermissionCheckInterval), (_serviceTimer) async {
|
||||||
(serviceTimer) async {
|
|
||||||
if (!super.serviceStopped) {
|
if (!super.serviceStopped) {
|
||||||
await checkEnabledAndPermission();
|
await checkEnabledAndPermission();
|
||||||
} else {
|
} else {
|
||||||
serviceTimer.cancel();
|
_serviceTimer.cancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_logger.d('PermissionService started');
|
_logger.d('PermissionService started');
|
||||||
|
@ -116,29 +122,6 @@ class PermissionService extends StoppableService {
|
||||||
_logger.d('PermissionService stopped');
|
_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() {
|
void _removeServiceCheckTimer() {
|
||||||
if (_serviceCheckTimer != null) {
|
if (_serviceCheckTimer != null) {
|
||||||
_serviceCheckTimer!.cancel();
|
_serviceCheckTimer!.cancel();
|
||||||
|
|
|
@ -3,8 +3,7 @@ import 'dart:async';
|
||||||
import '../enums/refresh_event.dart';
|
import '../enums/refresh_event.dart';
|
||||||
|
|
||||||
class RefreshService {
|
class RefreshService {
|
||||||
StreamController<RefreshEvent> refreshEventController =
|
StreamController<RefreshEvent> refreshEventController = StreamController<RefreshEvent>.broadcast();
|
||||||
StreamController<RefreshEvent>.broadcast();
|
|
||||||
|
|
||||||
void addEvent(RefreshEvent event) {
|
void addEvent(RefreshEvent event) {
|
||||||
if (refreshEventController.hasListener) {
|
if (refreshEventController.hasListener) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ class SessionService extends StoppableService {
|
||||||
Future<bool> login(String url, String apiKey) async {
|
Future<bool> login(String url, String apiKey) async {
|
||||||
setApiConfig(url, apiKey);
|
setApiConfig(url, apiKey);
|
||||||
|
|
||||||
var session = Session(url: url, apiKey: apiKey);
|
var session = new Session(url: url, apiKey: apiKey);
|
||||||
sessionController.add(session);
|
sessionController.add(session);
|
||||||
await _storageService.storeSession(session);
|
await _storageService.storeSession(session);
|
||||||
_logger.d('Session created');
|
_logger.d('Session created');
|
||||||
|
|
|
@ -5,36 +5,45 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/session.dart';
|
import '../models/session.dart';
|
||||||
|
|
||||||
class StorageService {
|
class StorageService {
|
||||||
static const _sessionKey = 'session';
|
static const _SESSION_KEY = 'session';
|
||||||
static const _lastUrlKey = 'last_url';
|
static const _LAST_URL_KEY = 'last_url';
|
||||||
|
static const _STORAGE_PERMISSION_DIALOG_IGNORED = 'storage_permission_ignored';
|
||||||
|
|
||||||
Future<bool> storeLastUrl(String url) {
|
Future<bool> storeLastUrl(String url) {
|
||||||
return _store(_lastUrlKey, url);
|
return _store(_LAST_URL_KEY, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> retrieveLastUrl() async {
|
Future<String?> retrieveLastUrl() async {
|
||||||
return await _retrieve(_lastUrlKey);
|
return await _retrieve(_LAST_URL_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasLastUrl() async {
|
Future<bool> hasLastUrl() async {
|
||||||
return await _exists(_lastUrlKey);
|
return await _exists(_LAST_URL_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> storeSession(Session session) {
|
Future<bool> storeSession(Session session) {
|
||||||
return _store(_sessionKey, json.encode(session));
|
return _store(_SESSION_KEY, json.encode(session));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Session> retrieveSession() async {
|
Future<Session> retrieveSession() async {
|
||||||
var retrieve = await _retrieve(_sessionKey);
|
var retrieve = await _retrieve(_SESSION_KEY);
|
||||||
return Session.fromJson(json.decode(retrieve!));
|
return Session.fromJson(json.decode(retrieve!));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasSession() {
|
Future<bool> hasSession() {
|
||||||
return _exists(_sessionKey);
|
return _exists(_SESSION_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> removeSession() {
|
Future<bool> removeSession() {
|
||||||
return _remove(_sessionKey);
|
return _remove(_SESSION_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> storeStoragePermissionDialogIgnored() {
|
||||||
|
return _store(_STORAGE_PERMISSION_DIALOG_IGNORED, true.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> hasStoragePermissionDialogIgnored() {
|
||||||
|
return _exists(_STORAGE_PERMISSION_DIALOG_IGNORED);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _exists(String key) async {
|
Future<bool> _exists(String key) async {
|
||||||
|
|
|
@ -12,10 +12,9 @@ class UserService {
|
||||||
final FileService _fileService = locator<FileService>();
|
final FileService _fileService = locator<FileService>();
|
||||||
final UserRepository _userRepository = locator<UserRepository>();
|
final UserRepository _userRepository = locator<UserRepository>();
|
||||||
|
|
||||||
Future<CreateApiKeyResponse> createApiKey(String url, String username,
|
Future<CreateApiKeyResponse> createApiKey(
|
||||||
String password, String accessLevel, String comment) async {
|
String url, String username, String password, String accessLevel, String comment) async {
|
||||||
return await _userRepository.postApiKey(
|
return await _userRepository.postApiKey(url, username, password, accessLevel, comment);
|
||||||
url, username, password, accessLevel, comment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ApiKeysResponse> getApiKeys() async {
|
Future<ApiKeysResponse> getApiKeys() async {
|
||||||
|
@ -27,7 +26,7 @@ class UserService {
|
||||||
try {
|
try {
|
||||||
await _fileService.getHistory();
|
await _fileService.getHistory();
|
||||||
} on ServiceException catch (e) {
|
} on ServiceException catch (e) {
|
||||||
throw ServiceException(code: ErrorCode.invalidApiKey, message: e.message);
|
throw new ServiceException(code: ErrorCode.INVALID_API_KEY, message: e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,13 @@ class FormatterUtil {
|
||||||
/// Format epoch timestamp
|
/// Format epoch timestamp
|
||||||
static String formatEpoch(num millis) {
|
static String formatEpoch(num millis) {
|
||||||
DateFormat dateFormat = DateFormat().add_yMEd().add_Hm();
|
DateFormat dateFormat = DateFormat().add_yMEd().add_Hm();
|
||||||
return dateFormat
|
return dateFormat.format(DateTime.fromMillisecondsSinceEpoch(millis as int));
|
||||||
.format(DateTime.fromMillisecondsSinceEpoch(millis as int));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String formatBytes(int bytes, int decimals) {
|
static String formatBytes(int bytes, int decimals) {
|
||||||
if (bytes <= 0) return "0 B";
|
if (bytes <= 0) return "0 B";
|
||||||
const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
var i = (log(bytes) / log(1024)).floor();
|
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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,10 @@ class AboutModel extends BaseModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initPackageInfo() async {
|
Future<void> _initPackageInfo() async {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
final PackageInfo info = await PackageInfo.fromPlatform();
|
final PackageInfo info = await PackageInfo.fromPlatform();
|
||||||
packageInfo = info;
|
packageInfo = info;
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openLink(String link) {
|
void openLink(String link) {
|
||||||
|
|
|
@ -5,21 +5,18 @@ import '../../core/util/logger.dart';
|
||||||
import '../enums/viewstate.dart';
|
import '../enums/viewstate.dart';
|
||||||
|
|
||||||
class BaseModel extends ChangeNotifier {
|
class BaseModel extends ChangeNotifier {
|
||||||
static const String stateViewKey = 'viewState';
|
static const String STATE_VIEW = 'viewState';
|
||||||
static const String stateMessageKey = 'viewMessage';
|
static const String STATE_MESSAGE = 'viewMessage';
|
||||||
|
|
||||||
final Logger _logger = getLogger();
|
final Logger _logger = getLogger();
|
||||||
|
|
||||||
bool _isDisposed = false;
|
bool _isDisposed = false;
|
||||||
|
|
||||||
final Map<String, Object?> _stateMap = {
|
Map<String, Object?> _stateMap = {STATE_VIEW: ViewState.Idle, STATE_MESSAGE: null};
|
||||||
stateViewKey: ViewState.idle,
|
|
||||||
stateMessageKey: null
|
|
||||||
};
|
|
||||||
|
|
||||||
ViewState? get state => _stateMap[stateViewKey] as ViewState?;
|
ViewState? get state => _stateMap[STATE_VIEW] as ViewState?;
|
||||||
|
|
||||||
String? get stateMessage => _stateMap[stateMessageKey] as String?;
|
String? get stateMessage => _stateMap[STATE_MESSAGE] as String?;
|
||||||
|
|
||||||
bool getStateValueAsBoolean(String key) {
|
bool getStateValueAsBoolean(String key) {
|
||||||
if (_stateMap.containsKey(key) && _stateMap[key] is bool) {
|
if (_stateMap.containsKey(key) && _stateMap[key] is bool) {
|
||||||
|
@ -45,14 +42,11 @@ class BaseModel extends ChangeNotifier {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStateBoolValue(String key, bool stateValue) =>
|
void setStateBoolValue(String key, bool stateValue) => _setStateValue(key, stateValue);
|
||||||
_setStateValue(key, stateValue);
|
|
||||||
|
|
||||||
void setStateIntValue(String key, int? stateValue) =>
|
void setStateIntValue(String key, int? stateValue) => _setStateValue(key, stateValue);
|
||||||
_setStateValue(key, stateValue);
|
|
||||||
|
|
||||||
void setStateStringValue(String key, String? stateValue) =>
|
void setStateStringValue(String key, String? stateValue) => _setStateValue(key, stateValue);
|
||||||
_setStateValue(key, stateValue);
|
|
||||||
|
|
||||||
void _setStateValue(String key, Object? stateValue) {
|
void _setStateValue(String key, Object? stateValue) {
|
||||||
if (_stateMap.containsKey(key)) {
|
if (_stateMap.containsKey(key)) {
|
||||||
|
@ -63,8 +57,7 @@ class BaseModel extends ChangeNotifier {
|
||||||
|
|
||||||
if (!_isDisposed) {
|
if (!_isDisposed) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_logger
|
_logger.d("Notified state value update '($key, ${stateValue.toString()})'");
|
||||||
.d("Notified state value update '($key, ${stateValue.toString()})'");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,16 +71,15 @@ class BaseModel extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStateView(ViewState stateView) {
|
void setStateView(ViewState stateView) {
|
||||||
_setStateValue(stateViewKey, stateView);
|
_setStateValue(STATE_VIEW, stateView);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStateMessage(String? stateMessage) {
|
void setStateMessage(String? stateMessage) {
|
||||||
_setStateValue(stateMessageKey, stateMessage);
|
_setStateValue(STATE_MESSAGE, stateMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_logger.d("Calling dispose");
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,9 +34,8 @@ class HistoryModel extends BaseModel {
|
||||||
String? errorMessage;
|
String? errorMessage;
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
_refreshTriggerSubscription =
|
_refreshTriggerSubscription = _refreshService.refreshEventController.stream.listen((event) {
|
||||||
_refreshService.refreshEventController.stream.listen((event) {
|
if (event == RefreshEvent.RefreshHistory) {
|
||||||
if (event == RefreshEvent.refreshHistory) {
|
|
||||||
_logger.d('History needs a refresh');
|
_logger.d('History needs a refresh');
|
||||||
getHistory();
|
getHistory();
|
||||||
}
|
}
|
||||||
|
@ -44,19 +43,18 @@ class HistoryModel extends BaseModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future getHistory() async {
|
Future getHistory() async {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pastes.clear();
|
pastes.clear();
|
||||||
History history = await _fileService.getHistory();
|
History _history = await _fileService.getHistory();
|
||||||
if (history.items.isNotEmpty) {
|
if (_history.items.isNotEmpty) {
|
||||||
history.items.forEach((key, value) {
|
_history.items.forEach((key, value) {
|
||||||
var millisecondsSinceEpoch = int.parse(value.date) * 1000;
|
var millisecondsSinceEpoch = int.parse(value.date) * 1000;
|
||||||
pastes.add(
|
pastes.add(
|
||||||
UploadedPaste(
|
UploadedPaste(
|
||||||
id: key,
|
id: key,
|
||||||
date:
|
date: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch),
|
||||||
DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch),
|
|
||||||
filename: value.filename,
|
filename: value.filename,
|
||||||
filesize: int.parse(value.filesize),
|
filesize: int.parse(value.filesize),
|
||||||
hash: value.hash,
|
hash: value.hash,
|
||||||
|
@ -68,8 +66,8 @@ class HistoryModel extends BaseModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (history.multipasteItems.isNotEmpty) {
|
if (_history.multipasteItems.isNotEmpty) {
|
||||||
history.multipasteItems.forEach((key, multiPaste) {
|
_history.multipasteItems.forEach((key, multiPaste) {
|
||||||
var millisecondsSinceEpoch = int.parse(multiPaste.date) * 1000;
|
var millisecondsSinceEpoch = int.parse(multiPaste.date) * 1000;
|
||||||
pastes.add(UploadedPaste(
|
pastes.add(UploadedPaste(
|
||||||
id: key,
|
id: key,
|
||||||
|
@ -92,35 +90,32 @@ class HistoryModel extends BaseModel {
|
||||||
e.responseBody is RestError &&
|
e.responseBody is RestError &&
|
||||||
e.responseBody.message != null) {
|
e.responseBody.message != null) {
|
||||||
if (e.statusCode == HttpStatus.badRequest) {
|
if (e.statusCode == HttpStatus.badRequest) {
|
||||||
errorMessage = translate('api.bad_request',
|
errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message});
|
||||||
args: {'reason': e.responseBody.message});
|
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error_payload',
|
errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message});
|
||||||
args: {'message': e.responseBody.message});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
errorMessage = translate('api.socket_timeout');
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('app.unknown_error');
|
errorMessage = translate('app.unknown_error');
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
_logger.e('An unknown error occurred', error: e);
|
_logger.e('An unknown error occurred', e);
|
||||||
rethrow;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future deletePaste(String id) async {
|
Future deletePaste(String id) async {
|
||||||
DialogResponse res = await _dialogService.showConfirmationDialog(
|
DialogResponse res = await _dialogService.showConfirmationDialog(
|
||||||
title: translate('history.delete_dialog.title'),
|
title: translate('history.delete_dialog.title'),
|
||||||
description:
|
description: translate('history.delete_dialog.description', args: {'id': id}),
|
||||||
translate('history.delete_dialog.description', args: {'id': id}),
|
|
||||||
buttonTitleAccept: translate('history.delete_dialog.accept'),
|
buttonTitleAccept: translate('history.delete_dialog.accept'),
|
||||||
buttonTitleDeny: translate('history.delete_dialog.deny'));
|
buttonTitleDeny: translate('history.delete_dialog.deny'));
|
||||||
|
|
||||||
|
@ -128,7 +123,7 @@ class HistoryModel extends BaseModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _fileService.deletePaste(id);
|
await _fileService.deletePaste(id);
|
||||||
|
@ -144,24 +139,23 @@ class HistoryModel extends BaseModel {
|
||||||
e.statusCode != HttpStatus.forbidden &&
|
e.statusCode != HttpStatus.forbidden &&
|
||||||
e.responseBody is RestError &&
|
e.responseBody is RestError &&
|
||||||
e.responseBody.message != null) {
|
e.responseBody.message != null) {
|
||||||
errorMessage = translate('api.general_rest_error_payload',
|
errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message});
|
||||||
args: {'message': e.responseBody.message});
|
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
errorMessage = translate('api.socket_timeout');
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('app.unknown_error');
|
errorMessage = translate('app.unknown_error');
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
_logger.e('An unknown error occurred', error: e);
|
_logger.e('An unknown error occurred', e);
|
||||||
rethrow;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openLink(String link) {
|
void openLink(String link) {
|
||||||
|
|
|
@ -20,12 +20,12 @@ import '../util/logger.dart';
|
||||||
import 'base_model.dart';
|
import 'base_model.dart';
|
||||||
|
|
||||||
class LoginModel extends BaseModel {
|
class LoginModel extends BaseModel {
|
||||||
TextEditingController _uriController = TextEditingController();
|
TextEditingController _uriController = new TextEditingController();
|
||||||
|
|
||||||
final TextEditingController _userNameController = TextEditingController();
|
final TextEditingController _userNameController = new TextEditingController();
|
||||||
final TextEditingController _passwordController = TextEditingController();
|
final TextEditingController _passwordController = new TextEditingController();
|
||||||
|
|
||||||
final TextEditingController _apiKeyController = TextEditingController();
|
final TextEditingController _apiKeyController = new TextEditingController();
|
||||||
|
|
||||||
TextEditingController get uriController => _uriController;
|
TextEditingController get uriController => _uriController;
|
||||||
|
|
||||||
|
@ -44,23 +44,23 @@ class LoginModel extends BaseModel {
|
||||||
String? errorMessage;
|
String? errorMessage;
|
||||||
|
|
||||||
void toggleLoginMethod() {
|
void toggleLoginMethod() {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
useCredentialsLogin = !useCredentialsLogin;
|
useCredentialsLogin = !useCredentialsLogin;
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() async {
|
void init() async {
|
||||||
bool hasLastUrl = await _storageService.hasLastUrl();
|
bool hasLastUrl = await _storageService.hasLastUrl();
|
||||||
|
|
||||||
if (hasLastUrl) {
|
if (hasLastUrl) {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
var s = await (_storageService.retrieveLastUrl() as FutureOr<String>);
|
var s = await (_storageService.retrieveLastUrl() as FutureOr<String>);
|
||||||
|
|
||||||
if (s.isNotEmpty) {
|
if (s.isNotEmpty) {
|
||||||
_uriController = TextEditingController(text: s);
|
_uriController = new TextEditingController(text: s);
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,45 +70,45 @@ class LoginModel extends BaseModel {
|
||||||
var password = passwordController.text;
|
var password = passwordController.text;
|
||||||
var apiKey = apiKeyController.text;
|
var apiKey = apiKeyController.text;
|
||||||
|
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
url = trim(url);
|
url = trim(url);
|
||||||
username = trim(username);
|
username = trim(username);
|
||||||
|
|
||||||
if (url.isEmpty) {
|
if (url.isEmpty) {
|
||||||
errorMessage = translate('login.errors.empty_url');
|
errorMessage = translate('login.errors.empty_url');
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url.contains("https://") && !url.contains("http://")) {
|
if (!url.contains("https://") && !url.contains("http://")) {
|
||||||
errorMessage = translate('login.errors.no_protocol');
|
errorMessage = translate('login.errors.no_protocol');
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool validUri = Uri.parse(url).isAbsolute;
|
bool validUri = Uri.parse(url).isAbsolute;
|
||||||
if (!validUri || !isURL(url)) {
|
if (!validUri || !isURL(url)) {
|
||||||
errorMessage = translate('login.errors.invalid_url');
|
errorMessage = translate('login.errors.invalid_url');
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useCredentialsLogin) {
|
if (useCredentialsLogin) {
|
||||||
if (username.isEmpty) {
|
if (username.isEmpty) {
|
||||||
errorMessage = translate('login.errors.empty_username');
|
errorMessage = translate('login.errors.empty_username');
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.isEmpty) {
|
if (password.isEmpty) {
|
||||||
errorMessage = translate('login.errors.empty_password');
|
errorMessage = translate('login.errors.empty_password');
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (apiKey.isEmpty) {
|
if (apiKey.isEmpty) {
|
||||||
errorMessage = translate('login.errors.empty_apikey');
|
errorMessage = translate('login.errors.empty_apikey');
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,19 +117,14 @@ class LoginModel extends BaseModel {
|
||||||
try {
|
try {
|
||||||
if (useCredentialsLogin) {
|
if (useCredentialsLogin) {
|
||||||
CreateApiKeyResponse apiKeyResponse = await _userService.createApiKey(
|
CreateApiKeyResponse apiKeyResponse = await _userService.createApiKey(
|
||||||
url,
|
url, username, password, 'apikey', 'fbmobile-${new DateTime.now().millisecondsSinceEpoch}');
|
||||||
username,
|
|
||||||
password,
|
|
||||||
'apikey',
|
|
||||||
'fbmobile-${DateTime.now().millisecondsSinceEpoch}');
|
|
||||||
|
|
||||||
var newKey = apiKeyResponse.data['new_key'];
|
var newKey = apiKeyResponse.data['new_key'];
|
||||||
if (newKey != null) {
|
if (newKey != null) {
|
||||||
success = await _sessionService.login(url, newKey);
|
success = await _sessionService.login(url, newKey);
|
||||||
} else {
|
} else {
|
||||||
throw ServiceException(
|
throw new ServiceException(
|
||||||
code: ErrorCode.invalidApiKey,
|
code: ErrorCode.INVALID_API_KEY, message: translate('login.errors.invalid_api_key'));
|
||||||
message: translate('login.errors.invalid_api_key'));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_sessionService.setApiConfig(url, apiKey);
|
_sessionService.setApiConfig(url, apiKey);
|
||||||
|
@ -141,37 +136,35 @@ class LoginModel extends BaseModel {
|
||||||
if (e is RestServiceException) {
|
if (e is RestServiceException) {
|
||||||
if (e.statusCode == HttpStatus.unauthorized) {
|
if (e.statusCode == HttpStatus.unauthorized) {
|
||||||
errorMessage = translate('login.errors.wrong_credentials');
|
errorMessage = translate('login.errors.wrong_credentials');
|
||||||
} else if (e.statusCode != HttpStatus.unauthorized &&
|
} else if (e.statusCode != HttpStatus.unauthorized && e.statusCode == HttpStatus.forbidden) {
|
||||||
e.statusCode == HttpStatus.forbidden) {
|
|
||||||
errorMessage = translate('login.errors.forbidden');
|
errorMessage = translate('login.errors.forbidden');
|
||||||
} else if (e.statusCode == HttpStatus.notFound) {
|
} else if (e.statusCode == HttpStatus.notFound) {
|
||||||
errorMessage = translate('api.incompatible_error_not_found');
|
errorMessage = translate('api.incompatible_error_not_found');
|
||||||
}
|
}
|
||||||
if (e.statusCode == HttpStatus.badRequest) {
|
if (e.statusCode == HttpStatus.badRequest) {
|
||||||
errorMessage = translate('api.bad_request',
|
errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message});
|
||||||
args: {'reason': e.responseBody.message});
|
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.invalidApiKey) {
|
} else if (e is ServiceException && e.code == ErrorCode.INVALID_API_KEY) {
|
||||||
errorMessage = translate('login.errors.invalid_api_key');
|
errorMessage = translate('login.errors.invalid_api_key');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
errorMessage = translate('api.socket_timeout');
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('app.unknown_error');
|
errorMessage = translate('app.unknown_error');
|
||||||
_sessionService.logout();
|
_sessionService.logout();
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
_logger.e('An unknown error occurred', error: e);
|
_logger.e('An unknown error occurred', e);
|
||||||
rethrow;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMessage!.isNotEmpty) {
|
if (errorMessage!.isNotEmpty) {
|
||||||
_sessionService.logout();
|
_sessionService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,7 @@ class ProfileModel extends BaseModel {
|
||||||
|
|
||||||
Future logout() async {
|
Future logout() async {
|
||||||
var dialogResult = await _dialogService.showConfirmationDialog(
|
var dialogResult = await _dialogService.showConfirmationDialog(
|
||||||
title: translate('logout.title'),
|
title: translate('logout.title'), description: translate('logout.confirm'));
|
||||||
description: translate('logout.confirm'));
|
|
||||||
|
|
||||||
if (dialogResult.confirmed!) {
|
if (dialogResult.confirmed!) {
|
||||||
await _sessionService.logout();
|
await _sessionService.logout();
|
||||||
|
@ -42,8 +41,7 @@ class ProfileModel extends BaseModel {
|
||||||
Future revealApiKey(String? apiKey) async {
|
Future revealApiKey(String? apiKey) async {
|
||||||
await _dialogService.showDialog(
|
await _dialogService.showDialog(
|
||||||
title: translate('profile.revealed_api_key.title'),
|
title: translate('profile.revealed_api_key.title'),
|
||||||
description: translate('profile.revealed_api_key.description',
|
description: translate('profile.revealed_api_key.description', args: {'apiKey': apiKey}));
|
||||||
args: {'apiKey': apiKey}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future showConfig(String url) async {
|
Future showConfig(String url) async {
|
||||||
|
@ -56,29 +54,27 @@ class ProfileModel extends BaseModel {
|
||||||
if (e is RestServiceException) {
|
if (e is RestServiceException) {
|
||||||
if (e.statusCode == HttpStatus.unauthorized) {
|
if (e.statusCode == HttpStatus.unauthorized) {
|
||||||
errorMessage = translate('login.errors.wrong_credentials');
|
errorMessage = translate('login.errors.wrong_credentials');
|
||||||
} else if (e.statusCode != HttpStatus.unauthorized &&
|
} else if (e.statusCode != HttpStatus.unauthorized && e.statusCode == HttpStatus.forbidden) {
|
||||||
e.statusCode == HttpStatus.forbidden) {
|
|
||||||
errorMessage = translate('login.errors.forbidden');
|
errorMessage = translate('login.errors.forbidden');
|
||||||
} else if (e.statusCode == HttpStatus.notFound) {
|
} else if (e.statusCode == HttpStatus.notFound) {
|
||||||
errorMessage = translate('api.incompatible_error_not_found');
|
errorMessage = translate('api.incompatible_error_not_found');
|
||||||
}
|
}
|
||||||
if (e.statusCode == HttpStatus.badRequest) {
|
if (e.statusCode == HttpStatus.badRequest) {
|
||||||
errorMessage = translate('api.bad_request',
|
errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message});
|
||||||
args: {'reason': e.responseBody.message});
|
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
errorMessage = translate('api.socket_timeout');
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('app.unknown_error');
|
errorMessage = translate('app.unknown_error');
|
||||||
setStateBoolValue(_configurationButtonLoading, false);
|
setStateBoolValue(_configurationButtonLoading, false);
|
||||||
_sessionService.logout();
|
_sessionService.logout();
|
||||||
setStateBoolValue(_configurationButtonLoading, false);
|
setStateBoolValue(_configurationButtonLoading, false);
|
||||||
_logger.e('An unknown error occurred', error: e);
|
_logger.e('An unknown error occurred', e);
|
||||||
rethrow;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,18 +84,15 @@ class ProfileModel extends BaseModel {
|
||||||
await _dialogService.showDialog(
|
await _dialogService.showDialog(
|
||||||
title: translate('profile.shown_config.title'),
|
title: translate('profile.shown_config.title'),
|
||||||
description: translate('profile.shown_config.description', args: {
|
description: translate('profile.shown_config.description', args: {
|
||||||
'uploadMaxSize':
|
'uploadMaxSize': FormatterUtil.formatBytes(config.uploadMaxSize as int, 2),
|
||||||
FormatterUtil.formatBytes(config.uploadMaxSize as int, 2),
|
|
||||||
'maxFilesPerRequest': config.maxFilesPerRequest,
|
'maxFilesPerRequest': config.maxFilesPerRequest,
|
||||||
'maxInputVars': config.maxInputVars,
|
'maxInputVars': config.maxInputVars,
|
||||||
'requestMaxSize':
|
'requestMaxSize': FormatterUtil.formatBytes(config.requestMaxSize as int, 2)
|
||||||
FormatterUtil.formatBytes(config.requestMaxSize as int, 2)
|
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
await _dialogService.showDialog(
|
await _dialogService.showDialog(
|
||||||
title: translate('profile.shown_config.error.title'),
|
title: translate('profile.shown_config.error.title'),
|
||||||
description: translate('profile.shown_config.error.description',
|
description: translate('profile.shown_config.error.description', args: {'message': errorMessage}));
|
||||||
args: {'message': errorMessage}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:fbmobile/core/services/permission_service.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
|
@ -10,21 +9,19 @@ import 'base_model.dart';
|
||||||
|
|
||||||
class StartUpViewModel extends BaseModel {
|
class StartUpViewModel extends BaseModel {
|
||||||
final SessionService _sessionService = locator<SessionService>();
|
final SessionService _sessionService = locator<SessionService>();
|
||||||
final PermissionService _permissionService = locator<PermissionService>();
|
|
||||||
final NavigationService _navigationService = locator<NavigationService>();
|
final NavigationService _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
Future handleStartUpLogic() async {
|
Future handleStartUpLogic() async {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
setStateMessage(translate('startup.init'));
|
setStateMessage(translate('startup.init'));
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
await Future.delayed(Duration(milliseconds: 150));
|
||||||
|
|
||||||
setStateMessage(translate('startup.start_services'));
|
setStateMessage(translate('startup.start_services'));
|
||||||
await _sessionService.start();
|
await _sessionService.start();
|
||||||
await _permissionService.start();
|
await Future.delayed(Duration(milliseconds: 150));
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
_navigationService.navigateAndReplaceTo(HomeView.routeName);
|
_navigationService.navigateAndReplaceTo(HomeView.routeName);
|
||||||
|
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,10 @@ import 'dart:io';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||||
|
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
import '../enums/error_code.dart';
|
import '../enums/error_code.dart';
|
||||||
|
@ -32,7 +31,7 @@ class UploadModel extends BaseModel {
|
||||||
final LinkService _linkService = locator<LinkService>();
|
final LinkService _linkService = locator<LinkService>();
|
||||||
final RefreshService _refreshService = locator<RefreshService>();
|
final RefreshService _refreshService = locator<RefreshService>();
|
||||||
|
|
||||||
final TextEditingController _pasteTextController = TextEditingController();
|
TextEditingController _pasteTextController = TextEditingController();
|
||||||
bool pasteTextTouched = false;
|
bool pasteTextTouched = false;
|
||||||
|
|
||||||
late StreamSubscription _intentDataStreamSubscription;
|
late StreamSubscription _intentDataStreamSubscription;
|
||||||
|
@ -46,41 +45,6 @@ class UploadModel extends BaseModel {
|
||||||
|
|
||||||
TextEditingController get pasteTextController => _pasteTextController;
|
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() {
|
void init() {
|
||||||
_pasteTextController.addListener(() {
|
_pasteTextController.addListener(() {
|
||||||
pasteTextTouched = pasteTextController.text.isNotEmpty;
|
pasteTextTouched = pasteTextController.text.isNotEmpty;
|
||||||
|
@ -88,33 +52,73 @@ class UploadModel extends BaseModel {
|
||||||
});
|
});
|
||||||
|
|
||||||
// For sharing images coming from outside the app while the app is in the memory
|
// For sharing images coming from outside the app while the app is in the memory
|
||||||
_intentDataStreamSubscription = FlutterSharingIntent.instance
|
_intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream().listen((List<SharedMediaFile> value) {
|
||||||
.getMediaStream()
|
if (value.length > 0) {
|
||||||
.listen((List<SharedFile> value) {
|
setStateView(ViewState.Busy);
|
||||||
_logger.d("Retrieved ${value.length} files from intent");
|
paths = value.map((sharedFile) {
|
||||||
_parseIntentFiles(value);
|
return PlatformFile.fromMap({
|
||||||
|
'path': sharedFile.path,
|
||||||
|
'name': basename(sharedFile.path),
|
||||||
|
'size': File(sharedFile.path).lengthSync(),
|
||||||
|
'bytes': null
|
||||||
|
});
|
||||||
|
}).toList();
|
||||||
|
setStateView(ViewState.Idle);
|
||||||
|
}
|
||||||
}, onError: (err) {
|
}, onError: (err) {
|
||||||
_errorIntentHandle(err);
|
_errorIntentHandle(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
// For sharing images coming from outside the app while the app is closed
|
// For sharing images coming from outside the app while the app is closed
|
||||||
FlutterSharingIntent.instance
|
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
|
||||||
.getInitialSharing()
|
if (value.length > 0) {
|
||||||
.then((List<SharedFile> value) {
|
setStateView(ViewState.Busy);
|
||||||
_logger.d("Retrieved ${value.length} files from inactive intent");
|
paths = value.map((sharedFile) {
|
||||||
_parseIntentFiles(value);
|
return PlatformFile.fromMap({
|
||||||
|
'path': sharedFile.path,
|
||||||
|
'name': basename(sharedFile.path),
|
||||||
|
'size': File(sharedFile.path).lengthSync(),
|
||||||
|
'bytes': null
|
||||||
|
});
|
||||||
|
}).toList();
|
||||||
|
setStateView(ViewState.Idle);
|
||||||
|
}
|
||||||
|
}, onError: (err) {
|
||||||
|
_errorIntentHandle(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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.isNotEmpty) {
|
||||||
|
setStateView(ViewState.Busy);
|
||||||
|
pasteTextController.text = value;
|
||||||
|
setStateView(ViewState.Idle);
|
||||||
|
}
|
||||||
|
}, onError: (err) {
|
||||||
|
_errorIntentHandle(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
setStateView(ViewState.Busy);
|
||||||
|
pasteTextController.text = value;
|
||||||
|
setStateView(ViewState.Idle);
|
||||||
|
}
|
||||||
|
}, onError: (err) {
|
||||||
|
_errorIntentHandle(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _errorIntentHandle(err) {
|
void _errorIntentHandle(err) {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
errorMessage = translate('upload.retrieval_intent');
|
errorMessage = translate('upload.retrieval_intent');
|
||||||
_logger.e('Error while retrieving shared data: $err');
|
_logger.e('Error while retrieving shared data: $err');
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? generatePasteLinks(Map<String, bool>? uploads, String url) {
|
String? generatePasteLinks(Map<String, bool>? uploads, String url) {
|
||||||
if (uploads != null && uploads.isNotEmpty) {
|
if (uploads != null && uploads.length > 0) {
|
||||||
var links = '';
|
var links = '';
|
||||||
|
|
||||||
uploads.forEach((id, isMulti) {
|
uploads.forEach((id, isMulti) {
|
||||||
|
@ -130,13 +134,13 @@ class UploadModel extends BaseModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleCreateMulti() {
|
void toggleCreateMulti() {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
createMulti = !createMulti;
|
createMulti = !createMulti;
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openFileExplorer() async {
|
void openFileExplorer() async {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
setStateMessage(translate('upload.file_explorer_open'));
|
setStateMessage(translate('upload.file_explorer_open'));
|
||||||
loadingPath = true;
|
loadingPath = true;
|
||||||
|
|
||||||
|
@ -146,68 +150,62 @@ class UploadModel extends BaseModel {
|
||||||
allowMultiple: true,
|
allowMultiple: true,
|
||||||
withData: false,
|
withData: false,
|
||||||
withReadStream: true,
|
withReadStream: true,
|
||||||
allowedExtensions: (_extension?.isNotEmpty ?? false)
|
allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '').split(',') : null,
|
||||||
? _extension?.replaceAll(' ', '').split(',')
|
|
||||||
: null,
|
|
||||||
))
|
))
|
||||||
?.files;
|
?.files;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
_logger.e('Unsupported operation', error: e);
|
_logger.e('Unsupported operation', e);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
_logger.e('An unknown error occurred', error: ex);
|
_logger.e('An unknown error occurred', ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingPath = false;
|
loadingPath = false;
|
||||||
fileName = paths != null ? paths!.map((e) => e.name).toString() : '...';
|
fileName = paths != null ? paths!.map((e) => e.name).toString() : '...';
|
||||||
|
|
||||||
setStateMessage(null);
|
setStateMessage(null);
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearCachedFiles() async {
|
void clearCachedFiles() async {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
await FilePicker.platform.clearTemporaryFiles();
|
await FilePicker.platform.clearTemporaryFiles();
|
||||||
paths = null;
|
paths = null;
|
||||||
fileName = null;
|
fileName = null;
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, bool>?> upload() async {
|
Future<Map<String, bool>?> upload() async {
|
||||||
setStateView(ViewState.busy);
|
setStateView(ViewState.Busy);
|
||||||
setStateMessage(translate('upload.uploading_now'));
|
setStateMessage(translate('upload.uploading_now'));
|
||||||
|
|
||||||
Map<String, bool> uploadedPasteIds = {};
|
Map<String, bool> uploadedPasteIds = new Map();
|
||||||
try {
|
try {
|
||||||
List<File>? files;
|
List<File>? files;
|
||||||
Map<String, String>? additionalFiles;
|
Map<String, String>? additionalFiles;
|
||||||
|
|
||||||
if (pasteTextController.text.isNotEmpty) {
|
if (pasteTextController.text.isNotEmpty) {
|
||||||
additionalFiles = Map.from({
|
additionalFiles = Map.from(
|
||||||
'paste-${(DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt':
|
{'paste-${(new DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt': pasteTextController.text});
|
||||||
pasteTextController.text
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paths != null && paths!.isNotEmpty) {
|
if (paths != null && paths!.length > 0) {
|
||||||
files = paths!.map((e) => File(e.path!)).toList();
|
files = paths!.map((e) => new File(e.path!)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadedResponse response =
|
UploadedResponse response = await _fileService.uploadPaste(files, additionalFiles);
|
||||||
await _fileService.uploadPaste(files, additionalFiles);
|
response.data.ids.forEach((element) {
|
||||||
for (var element in response.data.ids) {
|
|
||||||
uploadedPasteIds.putIfAbsent(element, () => false);
|
uploadedPasteIds.putIfAbsent(element, () => false);
|
||||||
}
|
});
|
||||||
|
|
||||||
if (createMulti && response.data.ids.length > 1) {
|
if (createMulti && response.data.ids.length > 1) {
|
||||||
UploadedMultiResponse multiResponse =
|
UploadedMultiResponse multiResponse = await _fileService.uploadMultiPaste(response.data.ids);
|
||||||
await _fileService.uploadMultiPaste(response.data.ids);
|
|
||||||
uploadedPasteIds.putIfAbsent(multiResponse.data.urlId, () => true);
|
uploadedPasteIds.putIfAbsent(multiResponse.data.urlId, () => true);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCachedFiles();
|
clearCachedFiles();
|
||||||
_pasteTextController.clear();
|
_pasteTextController.clear();
|
||||||
_refreshService.addEvent(RefreshEvent.refreshHistory);
|
_refreshService.addEvent(RefreshEvent.RefreshHistory);
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
return uploadedPasteIds;
|
return uploadedPasteIds;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -221,30 +219,28 @@ class UploadModel extends BaseModel {
|
||||||
e.responseBody is RestError &&
|
e.responseBody is RestError &&
|
||||||
e.responseBody.message != null) {
|
e.responseBody.message != null) {
|
||||||
if (e.statusCode == HttpStatus.badRequest) {
|
if (e.statusCode == HttpStatus.badRequest) {
|
||||||
errorMessage = translate('api.bad_request',
|
errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message});
|
||||||
args: {'reason': e.responseBody.message});
|
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error_payload',
|
errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message});
|
||||||
args: {'message': e.responseBody.message});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
errorMessage = translate('api.socket_timeout');
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('app.unknown_error');
|
errorMessage = translate('app.unknown_error');
|
||||||
setStateMessage(null);
|
setStateMessage(null);
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
_logger.e('An unknown error occurred', error: e);
|
_logger.e('An unknown error occurred', e);
|
||||||
rethrow;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateMessage(null);
|
setStateMessage(null);
|
||||||
setStateView(ViewState.idle);
|
setStateView(ViewState.Idle);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,8 +252,6 @@ class UploadModel extends BaseModel {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_pasteTextController.dispose();
|
_pasteTextController.dispose();
|
||||||
_intentDataStreamSubscription.cancel();
|
_intentDataStreamSubscription.cancel();
|
||||||
FlutterSharingIntent.instance.reset();
|
|
||||||
paths = null;
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,7 @@ void main() async {
|
||||||
setupLogger(Level.info);
|
setupLogger(Level.info);
|
||||||
setupLocator();
|
setupLocator();
|
||||||
|
|
||||||
var delegate = await LocalizationDelegate.create(
|
var delegate = await LocalizationDelegate.create(fallbackLocale: 'en', supportedLocales: ['en', 'en_US']);
|
||||||
fallbackLocale: 'en', supportedLocales: ['en', 'en_US']);
|
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
runApp(LocalizedApp(delegate, MyApp()));
|
runApp(LocalizedApp(delegate, MyApp()));
|
||||||
|
|
|
@ -14,21 +14,20 @@ class AppRouter {
|
||||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||||
switch (settings.name) {
|
switch (settings.name) {
|
||||||
case StartUpView.routeName:
|
case StartUpView.routeName:
|
||||||
return MaterialPageRoute(builder: (_) => const StartUpView());
|
return MaterialPageRoute(builder: (_) => StartUpView());
|
||||||
case AboutView.routeName:
|
case AboutView.routeName:
|
||||||
return MaterialPageRoute(builder: (_) => const AboutView());
|
return MaterialPageRoute(builder: (_) => AboutView());
|
||||||
case HomeView.routeName:
|
case HomeView.routeName:
|
||||||
return MaterialPageRoute(builder: (_) => const TabBarContainerView());
|
return MaterialPageRoute(builder: (_) => TabBarContainerView());
|
||||||
case LoginView.routeName:
|
case LoginView.routeName:
|
||||||
return MaterialPageRoute(builder: (_) => LoginView());
|
return MaterialPageRoute(builder: (_) => LoginView());
|
||||||
case ProfileView.routeName:
|
case ProfileView.routeName:
|
||||||
return MaterialPageRoute(builder: (_) => const ProfileView());
|
return MaterialPageRoute(builder: (_) => ProfileView());
|
||||||
default:
|
default:
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (_) => Scaffold(
|
builder: (_) => Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text(translate('dev.no_route',
|
child: Text(translate('dev.no_route', args: {'route': settings.name})),
|
||||||
args: {'route': settings.name})),
|
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class UIHelper {
|
class UIHelper {
|
||||||
static const double _verticalSpaceSmall = 10.0;
|
static const double _VerticalSpaceSmall = 10.0;
|
||||||
static const double _verticalSpaceMedium = 20.0;
|
static const double _VerticalSpaceMedium = 20.0;
|
||||||
static const double _verticalSpaceLarge = 60.0;
|
static const double _VerticalSpaceLarge = 60.0;
|
||||||
|
|
||||||
static const double _horizontalSpaceSmall = 10.0;
|
static const double _HorizontalSpaceSmall = 10.0;
|
||||||
static const double _horizontalSpaceMedium = 20.0;
|
static const double _HorizontalSpaceMedium = 20.0;
|
||||||
static const double _horizontalSpaceLarge = 60.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() {
|
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() {
|
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() {
|
static Widget verticalSpaceLarge() {
|
||||||
return verticalSpace(_verticalSpaceLarge);
|
return verticalSpace(_VerticalSpaceLarge);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a vertical space equal to the [height] supplied
|
/// Returns a vertical space equal to the [height] supplied
|
||||||
|
@ -29,19 +29,19 @@ class UIHelper {
|
||||||
return Container(height: height);
|
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() {
|
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() {
|
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() {
|
static Widget horizontalSpaceLarge() {
|
||||||
return horizontalSpace(_horizontalSpaceLarge);
|
return horizontalSpace(HorizontalSpaceLarge);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a vertical space equal to the [width] supplied
|
/// Returns a vertical space equal to the [width] supplied
|
||||||
|
|
|
@ -12,8 +12,6 @@ import 'base_view.dart';
|
||||||
class AboutView extends StatelessWidget {
|
class AboutView extends StatelessWidget {
|
||||||
static const routeName = '/about';
|
static const routeName = '/about';
|
||||||
|
|
||||||
const AboutView({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final logo = Hero(
|
final logo = Hero(
|
||||||
|
@ -32,14 +30,13 @@ class AboutView extends StatelessWidget {
|
||||||
title: Text(translate('titles.about')),
|
title: Text(translate('titles.about')),
|
||||||
enableAbout: false,
|
enableAbout: false,
|
||||||
),
|
),
|
||||||
body: model.state == ViewState.busy
|
body: model.state == ViewState.Busy
|
||||||
? const Center(child: CircularProgressIndicator())
|
? Center(child: CircularProgressIndicator())
|
||||||
: Container(
|
: Container(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
padding: const EdgeInsets.only(
|
padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10, top: 10),
|
||||||
left: 10.0, right: 10.0, bottom: 10, top: 10),
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
Center(child: logo),
|
Center(child: logo),
|
||||||
|
@ -78,7 +75,7 @@ class AboutView extends StatelessWidget {
|
||||||
Center(
|
Center(
|
||||||
child: Linkify(
|
child: Linkify(
|
||||||
text: translate('about.website'),
|
text: translate('about.website'),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: LinkifyOptions(humanize: false),
|
||||||
onOpen: (link) => model.openLink(link.url),
|
onOpen: (link) => model.openLink(link.url),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,7 +10,7 @@ class BaseView<T extends BaseModel> extends StatefulWidget {
|
||||||
final Widget Function(BuildContext context, T model, Widget? child)? builder;
|
final Widget Function(BuildContext context, T model, Widget? child)? builder;
|
||||||
final Function(T)? onModelReady;
|
final Function(T)? onModelReady;
|
||||||
|
|
||||||
const BaseView({super.key, this.builder, this.onModelReady});
|
BaseView({this.builder, this.onModelReady});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_BaseViewState<T> createState() => _BaseViewState<T>();
|
_BaseViewState<T> createState() => _BaseViewState<T>();
|
||||||
|
@ -31,9 +31,7 @@ class _BaseViewState<T extends BaseModel> extends State<BaseView<T>> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider<T?>(
|
return ChangeNotifierProvider<T?>(create: (context) => model, child: Consumer<T>(builder: widget.builder!));
|
||||||
create: (context) => model,
|
|
||||||
child: Consumer<T>(builder: widget.builder!));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -18,8 +18,6 @@ import 'base_view.dart';
|
||||||
class HistoryView extends StatelessWidget {
|
class HistoryView extends StatelessWidget {
|
||||||
static const routeName = '/history';
|
static const routeName = '/history';
|
||||||
|
|
||||||
const HistoryView({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseView<HistoryModel>(
|
return BaseView<HistoryModel>(
|
||||||
|
@ -27,25 +25,23 @@ class HistoryView extends StatelessWidget {
|
||||||
model.init();
|
model.init();
|
||||||
return model.getHistory();
|
return model.getHistory();
|
||||||
},
|
},
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) =>
|
||||||
appBar: MyAppBar(title: Text(translate('titles.history'))),
|
Scaffold(appBar: MyAppBar(title: Text(translate('titles.history'))), body: _render(model, context)),
|
||||||
body: _render(model, context)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _render(HistoryModel model, BuildContext context) {
|
Widget _render(HistoryModel model, BuildContext context) {
|
||||||
var url = Provider.of<Session>(context).url;
|
var url = Provider.of<Session>(context).url;
|
||||||
|
|
||||||
return model.state == ViewState.busy
|
return model.state == ViewState.Busy
|
||||||
? const Center(child: CircularProgressIndicator())
|
? Center(child: CircularProgressIndicator())
|
||||||
: (model.errorMessage == null
|
: (model.errorMessage == null
|
||||||
? Container(
|
? Container(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: () async => await model.getHistory(),
|
onRefresh: () async => await model.getHistory(), child: _renderItems(model, url, context)))
|
||||||
child: _renderItems(model, url, context)))
|
|
||||||
: Container(
|
: Container(
|
||||||
padding: const EdgeInsets.all(25),
|
padding: EdgeInsets.all(25),
|
||||||
child: CenteredErrorRow(
|
child: CenteredErrorRow(
|
||||||
model.errorMessage,
|
model.errorMessage,
|
||||||
retryCallback: () => model.getHistory(),
|
retryCallback: () => model.getHistory(),
|
||||||
|
@ -55,16 +51,15 @@ class HistoryView extends StatelessWidget {
|
||||||
Widget _renderItems(HistoryModel model, String url, BuildContext context) {
|
Widget _renderItems(HistoryModel model, String url, BuildContext context) {
|
||||||
List<Widget> cards = [];
|
List<Widget> cards = [];
|
||||||
|
|
||||||
if (model.pastes.isNotEmpty) {
|
if (model.pastes.length > 0) {
|
||||||
for (var paste in model.pastes.reversed) {
|
model.pastes.reversed.forEach((paste) {
|
||||||
List<Widget> widgets = [];
|
List<Widget> widgets = [];
|
||||||
|
|
||||||
var fullPasteUrl = PasteUtil.generateLink(url, paste.id);
|
var fullPasteUrl = PasteUtil.generateLink(url, paste.id);
|
||||||
var openInBrowserButton = _renderOpenInBrowser(model, fullPasteUrl);
|
var openInBrowserButton = _renderOpenInBrowser(model, fullPasteUrl);
|
||||||
|
|
||||||
var dateWidget = ListTile(
|
var dateWidget = ListTile(
|
||||||
title: Text(
|
title: Text(FormatterUtil.formatEpoch(paste.date!.millisecondsSinceEpoch)),
|
||||||
FormatterUtil.formatEpoch(paste.date!.millisecondsSinceEpoch)),
|
|
||||||
subtitle: Text(translate('history.date')),
|
subtitle: Text(translate('history.date')),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -76,8 +71,7 @@ class HistoryView extends StatelessWidget {
|
||||||
var copyWidget = ListTile(
|
var copyWidget = ListTile(
|
||||||
title: Text(translate('history.copy_link.description')),
|
title: Text(translate('history.copy_link.description')),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.copy,
|
icon: Icon(Icons.copy, color: blueColor, textDirection: TextDirection.ltr),
|
||||||
color: blueColor, textDirection: TextDirection.ltr),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
FlutterClipboard.copy(fullPasteUrl).then((value) {
|
FlutterClipboard.copy(fullPasteUrl).then((value) {
|
||||||
final snackBar = SnackBar(
|
final snackBar = SnackBar(
|
||||||
|
@ -89,18 +83,16 @@ class HistoryView extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
content: Text(translate('history.copy_link.copied')),
|
content: Text(translate('history.copy_link.copied')),
|
||||||
duration: const Duration(seconds: 10),
|
duration: Duration(seconds: 10),
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var deleteWidget = ListTile(
|
var deleteWidget = ListTile(
|
||||||
title: Text(translate('history.delete')),
|
title: Text(translate('history.delete')),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.delete, color: redColor),
|
icon: Icon(Icons.delete, color: redColor),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.deletePaste(paste.id);
|
model.deletePaste(paste.id);
|
||||||
}));
|
}));
|
||||||
|
@ -128,13 +120,13 @@ class HistoryView extends StatelessWidget {
|
||||||
widgets.add(fileSizeWidget);
|
widgets.add(fileSizeWidget);
|
||||||
widgets.add(mimeTypeWidget);
|
widgets.add(mimeTypeWidget);
|
||||||
} else {
|
} else {
|
||||||
for (var element in paste.items!) {
|
paste.items!.forEach((element) {
|
||||||
widgets.add(ListTile(
|
widgets.add(ListTile(
|
||||||
title: Text(element!),
|
title: Text(element!),
|
||||||
subtitle: Text(translate('history.multipaste_element')),
|
subtitle: Text(translate('history.multipaste_element')),
|
||||||
trailing: _renderOpenInBrowser(model, '$url/$element'),
|
trailing: _renderOpenInBrowser(model, '$url/$element'),
|
||||||
));
|
));
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
widgets.add(dateWidget);
|
widgets.add(dateWidget);
|
||||||
|
@ -143,7 +135,7 @@ class HistoryView extends StatelessWidget {
|
||||||
widgets.add(deleteWidget);
|
widgets.add(deleteWidget);
|
||||||
|
|
||||||
var expandable = ExpandableTheme(
|
var expandable = ExpandableTheme(
|
||||||
data: const ExpandableThemeData(
|
data: ExpandableThemeData(
|
||||||
iconPlacement: ExpandablePanelIconPlacement.right,
|
iconPlacement: ExpandablePanelIconPlacement.right,
|
||||||
headerAlignment: ExpandablePanelHeaderAlignment.center,
|
headerAlignment: ExpandablePanelHeaderAlignment.center,
|
||||||
hasIcon: true,
|
hasIcon: true,
|
||||||
|
@ -154,7 +146,7 @@ class HistoryView extends StatelessWidget {
|
||||||
header: InkWell(
|
header: InkWell(
|
||||||
child: Text(
|
child: Text(
|
||||||
paste.id,
|
paste.id,
|
||||||
style: const TextStyle(color: blueColor),
|
style: TextStyle(color: blueColor),
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
)),
|
)),
|
||||||
expanded: Column(
|
expanded: Column(
|
||||||
|
@ -170,17 +162,15 @@ class HistoryView extends StatelessWidget {
|
||||||
trailing: Wrap(children: [
|
trailing: Wrap(children: [
|
||||||
openInBrowserButton,
|
openInBrowserButton,
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.share,
|
icon: Icon(Icons.share, color: blueColor, textDirection: TextDirection.ltr),
|
||||||
color: blueColor, textDirection: TextDirection.ltr),
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await Share.share(fullPasteUrl);
|
await Share.share(fullPasteUrl);
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
subtitle: Text(!paste.isMulti! ? paste.filename! : '',
|
subtitle: Text(!paste.isMulti! ? paste.filename! : '', style: TextStyle(fontStyle: FontStyle.italic)),
|
||||||
style: const TextStyle(fontStyle: FontStyle.italic)),
|
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
cards.add(Card(
|
cards.add(Card(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
@ -191,15 +181,14 @@ class HistoryView extends StatelessWidget {
|
||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
|
||||||
children: cards,
|
children: cards,
|
||||||
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _renderOpenInBrowser(HistoryModel model, String url) {
|
Widget _renderOpenInBrowser(HistoryModel model, String url) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: const Icon(Icons.open_in_new,
|
icon: Icon(Icons.open_in_new, color: blueColor, textDirection: TextDirection.ltr),
|
||||||
color: blueColor, textDirection: TextDirection.ltr),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
return model.openLink(url);
|
return model.openLink(url);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,16 +9,12 @@ import 'base_view.dart';
|
||||||
class HomeView extends StatelessWidget {
|
class HomeView extends StatelessWidget {
|
||||||
static const routeName = '/home';
|
static const routeName = '/home';
|
||||||
|
|
||||||
const HomeView({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseView<HomeModel>(
|
return BaseView<HomeModel>(
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
appBar: MyAppBar(title: Text(translate('app.title'))),
|
appBar: MyAppBar(title: Text(translate('app.title'))),
|
||||||
body: model.state == ViewState.busy
|
body: model.state == ViewState.Busy ? Center(child: CircularProgressIndicator()) : Container()),
|
||||||
? const Center(child: CircularProgressIndicator())
|
|
||||||
: Container()),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import '../../core/services/dialog_service.dart';
|
||||||
import '../../core/services/navigation_service.dart';
|
import '../../core/services/navigation_service.dart';
|
||||||
import '../../core/viewmodels/login_model.dart';
|
import '../../core/viewmodels/login_model.dart';
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
|
import '../../ui/shared/text_styles.dart';
|
||||||
import '../../ui/views/home_view.dart';
|
import '../../ui/views/home_view.dart';
|
||||||
import '../../ui/widgets/my_appbar.dart';
|
import '../../ui/widgets/my_appbar.dart';
|
||||||
import '../shared/app_colors.dart';
|
import '../shared/app_colors.dart';
|
||||||
|
@ -20,8 +21,6 @@ class LoginView extends StatelessWidget {
|
||||||
final NavigationService _navigationService = locator<NavigationService>();
|
final NavigationService _navigationService = locator<NavigationService>();
|
||||||
final DialogService _dialogService = locator<DialogService>();
|
final DialogService _dialogService = locator<DialogService>();
|
||||||
|
|
||||||
LoginView({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final logo = Hero(
|
final logo = Hero(
|
||||||
|
@ -37,11 +36,11 @@ class LoginView extends StatelessWidget {
|
||||||
onModelReady: (model) => model.init(),
|
onModelReady: (model) => model.init(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
appBar: MyAppBar(title: Text(translate('titles.login'))),
|
appBar: MyAppBar(title: Text(translate('titles.login'))),
|
||||||
body: model.state == ViewState.busy
|
body: model.state == ViewState.Busy
|
||||||
? const Center(child: CircularProgressIndicator())
|
? Center(child: CircularProgressIndicator())
|
||||||
: ListView(
|
: ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
|
padding: EdgeInsets.only(left: 10.0, right: 10.0),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
Center(child: logo),
|
Center(child: logo),
|
||||||
|
@ -51,22 +50,21 @@ class LoginView extends StatelessWidget {
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
translate('login.help'),
|
||||||
|
style: subHeaderStyle,
|
||||||
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
child: const Icon(Icons.help),
|
child: Icon(Icons.help),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_dialogService.showDialog(
|
_dialogService.showDialog(
|
||||||
title: translate(
|
title: translate('login.compatibility_dialog.title'),
|
||||||
'login.compatibility_dialog.title'),
|
description: translate('login.compatibility_dialog.body'));
|
||||||
description: translate(
|
|
||||||
'login.compatibility_dialog.body'));
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Icon(
|
child:
|
||||||
model.useCredentialsLogin
|
Icon(model.useCredentialsLogin ? Icons.person_outline : Icons.vpn_key, color: blueColor),
|
||||||
? Icons.person_outline
|
|
||||||
: Icons.vpn_key,
|
|
||||||
color: blueColor),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
model.toggleLoginMethod();
|
model.toggleLoginMethod();
|
||||||
},
|
},
|
||||||
|
@ -85,14 +83,12 @@ class LoginView extends StatelessWidget {
|
||||||
uriController: model.uriController,
|
uriController: model.uriController,
|
||||||
apiKeyController: model.apiKeyController),
|
apiKeyController: model.apiKeyController),
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
ElevatedButton.icon(
|
ElevatedButton(
|
||||||
icon: const Icon(Icons.login, color: blueColor),
|
child: Text(translate('login.button')),
|
||||||
label: Text(translate('login.button')),
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var loginSuccess = await model.login();
|
var loginSuccess = await model.login();
|
||||||
if (loginSuccess) {
|
if (loginSuccess) {
|
||||||
_navigationService
|
_navigationService.navigateAndReplaceTo(HomeView.routeName);
|
||||||
.navigateAndReplaceTo(HomeView.routeName);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,14 +8,11 @@ import '../shared/app_colors.dart';
|
||||||
import 'history_view.dart';
|
import 'history_view.dart';
|
||||||
|
|
||||||
class AuthenticatedNavBarView extends StatefulWidget {
|
class AuthenticatedNavBarView extends StatefulWidget {
|
||||||
const AuthenticatedNavBarView({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AuthenticatedNavBarState createState() => AuthenticatedNavBarState();
|
AuthenticatedNavBarState createState() => AuthenticatedNavBarState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthenticatedNavBarState extends State<AuthenticatedNavBarView>
|
class AuthenticatedNavBarState extends State<AuthenticatedNavBarView> with SingleTickerProviderStateMixin {
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
final Logger _logger = getLogger();
|
final Logger _logger = getLogger();
|
||||||
int _currentTabIndex = 0;
|
int _currentTabIndex = 0;
|
||||||
|
|
||||||
|
@ -55,17 +52,17 @@ class AuthenticatedNavBarState extends State<AuthenticatedNavBarView>
|
||||||
Container(
|
Container(
|
||||||
color: myColor,
|
color: myColor,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: const UploadView(),
|
child: UploadView(),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: myColor,
|
color: myColor,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: const HistoryView(),
|
child: HistoryView(),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: myColor,
|
color: myColor,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: const ProfileView(),
|
child: ProfileView(),
|
||||||
),
|
),
|
||||||
][_currentTabIndex],
|
][_currentTabIndex],
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,22 +15,19 @@ import 'base_view.dart';
|
||||||
class ProfileView extends StatelessWidget {
|
class ProfileView extends StatelessWidget {
|
||||||
static const routeName = '/profile';
|
static const routeName = '/profile';
|
||||||
|
|
||||||
const ProfileView({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseView<ProfileModel>(
|
return BaseView<ProfileModel>(
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) =>
|
||||||
appBar: MyAppBar(title: Text(translate('titles.profile'))),
|
Scaffold(appBar: MyAppBar(title: Text(translate('titles.profile'))), body: _render(model, context)));
|
||||||
body: _render(model, context)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _render(ProfileModel model, BuildContext context) {
|
Widget _render(ProfileModel model, BuildContext context) {
|
||||||
var url = Provider.of<Session>(context).url;
|
var url = Provider.of<Session>(context).url;
|
||||||
var apiKey = Provider.of<Session>(context).apiKey;
|
var apiKey = Provider.of<Session>(context).apiKey;
|
||||||
|
|
||||||
return model.state == ViewState.busy
|
return model.state == ViewState.Busy
|
||||||
? const Center(child: CircularProgressIndicator())
|
? Center(child: CircularProgressIndicator())
|
||||||
: ListView(
|
: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
|
@ -48,27 +45,25 @@ class ProfileView extends StatelessWidget {
|
||||||
child: Linkify(
|
child: Linkify(
|
||||||
onOpen: (link) => model.openLink(link.url),
|
onOpen: (link) => model.openLink(link.url),
|
||||||
text: translate('profile.connection', args: {'url': url}),
|
text: translate('profile.connection', args: {'url': url}),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: LinkifyOptions(humanize: false),
|
||||||
))),
|
))),
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||||
child: ElevatedButton.icon(
|
child: model.configLoading
|
||||||
icon: model.configLoading
|
? Center(
|
||||||
? Container(
|
child: Column(
|
||||||
width: 24,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
height: 24,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
padding: const EdgeInsets.all(2.0),
|
children: [
|
||||||
child: const CircularProgressIndicator(
|
CircularProgressIndicator(),
|
||||||
color: blueColor,
|
Text(translate('profile.show_config_loading')),
|
||||||
strokeWidth: 3,
|
],
|
||||||
),
|
))
|
||||||
)
|
: ElevatedButton.icon(
|
||||||
: const Icon(Icons.settings, color: blueColor),
|
icon: Icon(Icons.settings, color: blueColor),
|
||||||
label: Text(
|
label: Text(
|
||||||
model.configLoading
|
translate('profile.show_config'),
|
||||||
? translate('profile.show_config_loading')
|
|
||||||
: translate('profile.show_config'),
|
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await model.showConfig(url);
|
await model.showConfig(url);
|
||||||
|
@ -77,7 +72,7 @@ class ProfileView extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.lock, color: orangeColor),
|
icon: Icon(Icons.lock, color: orangeColor),
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('profile.reveal_api_key'),
|
translate('profile.reveal_api_key'),
|
||||||
),
|
),
|
||||||
|
@ -88,7 +83,7 @@ class ProfileView extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.exit_to_app, color: redColor),
|
icon: Icon(Icons.exit_to_app, color: redColor),
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('profile.logout'),
|
translate('profile.logout'),
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,28 +3,26 @@ import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
import '../../core/enums/viewstate.dart';
|
import '../../core/enums/viewstate.dart';
|
||||||
import '../../core/viewmodels/startup_model.dart';
|
import '../../core/viewmodels/startup_model.dart';
|
||||||
|
import '../shared/app_colors.dart';
|
||||||
|
|
||||||
class StartUpView extends StatelessWidget {
|
class StartUpView extends StatelessWidget {
|
||||||
static const routeName = '/';
|
static const routeName = '/';
|
||||||
|
|
||||||
const StartUpView({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<StartUpViewModel>.reactive(
|
return ViewModelBuilder<StartUpViewModel>.reactive(
|
||||||
viewModelBuilder: () => StartUpViewModel(),
|
viewModelBuilder: () => StartUpViewModel(),
|
||||||
onViewModelReady: (model) => model.handleStartUpLogic(),
|
onModelReady: (model) => model.handleStartUpLogic(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
body: model.state == ViewState.busy
|
backgroundColor: whiteColor,
|
||||||
|
body: model.state == ViewState.Busy
|
||||||
? Center(
|
? Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const CircularProgressIndicator(),
|
CircularProgressIndicator(),
|
||||||
(model.stateMessage!.isNotEmpty
|
(model.stateMessage!.isNotEmpty ? Text(model.stateMessage!) : Container())
|
||||||
? Text(model.stateMessage!)
|
|
||||||
: Container())
|
|
||||||
]))
|
]))
|
||||||
: Container()));
|
: Container()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,17 @@ import 'package:provider/provider.dart';
|
||||||
import '../../core/models/session.dart';
|
import '../../core/models/session.dart';
|
||||||
|
|
||||||
class TabBarContainerView extends StatelessWidget {
|
class TabBarContainerView extends StatelessWidget {
|
||||||
const TabBarContainerView({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Session? currentSession = Provider.of<Session?>(context);
|
Session? currentSession = Provider.of<Session?>(context);
|
||||||
bool isAuthenticated =
|
bool isAuthenticated = currentSession != null ? currentSession.apiKey.isNotEmpty : false;
|
||||||
currentSession != null ? currentSession.apiKey.isNotEmpty : false;
|
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return const AuthenticatedNavBarView();
|
return AuthenticatedNavBarView();
|
||||||
}
|
}
|
||||||
|
|
||||||
return LoginView();
|
return Container(
|
||||||
|
child: LoginView(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:clipboard/clipboard.dart';
|
import 'package:clipboard/clipboard.dart';
|
||||||
import 'package:fbmobile/core/util/formatter_util.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -15,35 +14,29 @@ import 'base_view.dart';
|
||||||
class UploadView extends StatelessWidget {
|
class UploadView extends StatelessWidget {
|
||||||
static const routeName = '/upload';
|
static const routeName = '/upload';
|
||||||
|
|
||||||
const UploadView({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseView<UploadModel>(
|
return BaseView<UploadModel>(
|
||||||
onModelReady: (model) => model.init(),
|
onModelReady: (model) => model.init(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) =>
|
||||||
appBar: MyAppBar(title: Text(translate('titles.upload'))),
|
Scaffold(appBar: MyAppBar(title: Text(translate('titles.upload'))), body: _render(model, context)));
|
||||||
body: _render(model, context)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isUploadButtonEnabled(UploadModel model) {
|
bool _isUploadButtonEnabled(UploadModel model) {
|
||||||
return model.pasteTextTouched ||
|
return model.pasteTextTouched || (model.paths != null && model.paths!.length > 0);
|
||||||
(model.paths != null && model.paths!.isNotEmpty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _render(UploadModel model, BuildContext context) {
|
Widget _render(UploadModel model, BuildContext context) {
|
||||||
var url = Provider.of<Session>(context).url;
|
var url = Provider.of<Session>(context).url;
|
||||||
|
|
||||||
return model.state == ViewState.busy
|
return model.state == ViewState.Busy
|
||||||
? Center(
|
? Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const CircularProgressIndicator(),
|
CircularProgressIndicator(),
|
||||||
(model.stateMessage != null && model.stateMessage!.isNotEmpty
|
(model.stateMessage != null && model.stateMessage!.isNotEmpty ? Text(model.stateMessage!) : Container())
|
||||||
? Text(model.stateMessage!)
|
|
||||||
: Container())
|
|
||||||
]))
|
]))
|
||||||
: ListView(children: <Widget>[
|
: ListView(children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -57,19 +50,16 @@ class UploadView extends StatelessWidget {
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: 7,
|
maxLines: 7,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: const Icon(
|
prefixIcon: Icon(
|
||||||
Icons.text_snippet,
|
Icons.text_snippet,
|
||||||
),
|
),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
onPressed: () =>
|
onPressed: () => model.pasteTextController.clear(),
|
||||||
model.pasteTextController.clear(),
|
icon: Icon(Icons.clear),
|
||||||
icon: const Icon(Icons.clear),
|
|
||||||
),
|
),
|
||||||
hintText: translate('upload.text_to_be_pasted'),
|
hintText: translate('upload.text_to_be_pasted'),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(
|
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||||
20.0, 10.0, 20.0, 10.0),
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(32.0)),
|
|
||||||
),
|
),
|
||||||
controller: model.pasteTextController)),
|
controller: model.pasteTextController)),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -85,17 +75,14 @@ class UploadView extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.file_copy_sharp,
|
icon: Icon(Icons.file_copy_sharp, color: blueColor),
|
||||||
color: blueColor),
|
|
||||||
onPressed: () => model.openFileExplorer(),
|
onPressed: () => model.openFileExplorer(),
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('upload.open_file_explorer'),
|
translate('upload.open_file_explorer'),
|
||||||
)),
|
)),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.cancel,
|
icon: Icon(Icons.cancel, color: orangeColor),
|
||||||
color: orangeColor),
|
onPressed: model.paths != null && model.paths!.length > 0
|
||||||
onPressed: model.paths != null &&
|
|
||||||
model.paths!.isNotEmpty
|
|
||||||
? () => model.clearCachedFiles()
|
? () => model.clearCachedFiles()
|
||||||
: null,
|
: null,
|
||||||
label: Text(
|
label: Text(
|
||||||
|
@ -125,107 +112,66 @@ class UploadView extends StatelessWidget {
|
||||||
onPressed: !_isUploadButtonEnabled(model)
|
onPressed: !_isUploadButtonEnabled(model)
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
Map<String, bool>? items =
|
Map<String, bool>? items = await model.upload();
|
||||||
await model.upload();
|
String? clipboardContent = model.generatePasteLinks(items, url);
|
||||||
String? clipboardContent = model
|
|
||||||
.generatePasteLinks(items, url);
|
|
||||||
|
|
||||||
if (clipboardContent != null &&
|
if (clipboardContent != null && clipboardContent.isNotEmpty) {
|
||||||
clipboardContent.isNotEmpty) {
|
FlutterClipboard.copy(clipboardContent).then((value) {
|
||||||
FlutterClipboard.copy(
|
|
||||||
clipboardContent)
|
|
||||||
.then((value) {
|
|
||||||
final snackBar = SnackBar(
|
final snackBar = SnackBar(
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: translate(
|
label: translate('upload.dismiss'),
|
||||||
'upload.dismiss'),
|
|
||||||
textColor: blueColor,
|
textColor: blueColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||||
context)
|
|
||||||
.hideCurrentSnackBar();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
content: Text(translate(
|
content: Text(translate('upload.uploaded')),
|
||||||
'upload.uploaded')),
|
duration: Duration(seconds: 10),
|
||||||
duration:
|
|
||||||
const Duration(seconds: 10),
|
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.upload_rounded,
|
icon: Icon(Icons.upload_rounded, color: greenColor),
|
||||||
color: greenColor),
|
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('upload.upload'),
|
translate('upload.upload'),
|
||||||
)),
|
)),
|
||||||
])),
|
])),
|
||||||
model.errorMessage != null && model.errorMessage!.isNotEmpty
|
model.errorMessage != null && model.errorMessage!.isNotEmpty
|
||||||
? (Padding(
|
? (Padding(
|
||||||
padding:
|
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||||
const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
|
||||||
child: CenteredErrorRow(model.errorMessage)))
|
child: CenteredErrorRow(model.errorMessage)))
|
||||||
: Container(),
|
: Container(),
|
||||||
Builder(
|
Builder(
|
||||||
builder: (BuildContext context) => model.loadingPath
|
builder: (BuildContext context) => model.loadingPath
|
||||||
? const Padding(
|
? Padding(
|
||||||
padding: EdgeInsets.only(bottom: 10.0),
|
padding: const EdgeInsets.only(bottom: 10.0),
|
||||||
child: CircularProgressIndicator(),
|
child: const CircularProgressIndicator(),
|
||||||
)
|
)
|
||||||
: model.paths != null
|
: model.paths != null
|
||||||
? Container(
|
? Container(
|
||||||
padding: const EdgeInsets.only(bottom: 20.0),
|
padding: const EdgeInsets.only(bottom: 30.0),
|
||||||
height:
|
height: MediaQuery.of(context).size.height * 0.50,
|
||||||
MediaQuery.of(context).size.height * 0.50,
|
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
itemCount: model.paths != null &&
|
itemCount: model.paths != null && model.paths!.isNotEmpty ? model.paths!.length : 1,
|
||||||
model.paths!.isNotEmpty
|
itemBuilder: (BuildContext context, int index) {
|
||||||
? model.paths!.length
|
final bool isMultiPath = model.paths != null && model.paths!.isNotEmpty;
|
||||||
: 1,
|
|
||||||
itemBuilder:
|
|
||||||
(BuildContext context, int index) {
|
|
||||||
final bool isMultiPath =
|
|
||||||
model.paths != null &&
|
|
||||||
model.paths!.isNotEmpty;
|
|
||||||
final String name = (isMultiPath
|
final String name = (isMultiPath
|
||||||
? model.paths!
|
? model.paths!.map((e) => e.name).toList()[index]
|
||||||
.map((e) => e.name)
|
|
||||||
.toList()[index]
|
|
||||||
: model.fileName ?? '...');
|
: model.fileName ?? '...');
|
||||||
final size = model.paths!.isNotEmpty
|
final path = model.paths!.length > 0
|
||||||
? model.paths!
|
? model.paths!.map((e) => e.path).toList()[index].toString()
|
||||||
.map((e) => e.size)
|
|
||||||
.toList()[index]
|
|
||||||
.toString()
|
|
||||||
: '';
|
|
||||||
final path = model.paths!.isNotEmpty
|
|
||||||
? model.paths!
|
|
||||||
.map((e) => e.path)
|
|
||||||
.toList()[index]
|
|
||||||
.toString()
|
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.clear,
|
|
||||||
color: orangeColor),
|
|
||||||
onPressed: () {
|
|
||||||
model.deleteIntentFile(path);
|
|
||||||
}),
|
|
||||||
title: Text(
|
title: Text(
|
||||||
"$name (${FormatterUtil.formatBytes(int.parse(size), 2)})",
|
name,
|
||||||
),
|
),
|
||||||
subtitle: Text(path),
|
subtitle: Text(path),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
separatorBuilder:
|
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
||||||
(BuildContext context, int index) =>
|
|
||||||
const Divider(),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
|
|
|
@ -3,16 +3,18 @@ import 'package:flutter/material.dart';
|
||||||
import '../../core/services/navigation_service.dart';
|
import '../../core/services/navigation_service.dart';
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
import '../../ui/views/about_view.dart';
|
import '../../ui/views/about_view.dart';
|
||||||
|
import '../shared/app_colors.dart';
|
||||||
|
|
||||||
class AboutIconButton extends StatelessWidget {
|
class AboutIconButton extends StatelessWidget {
|
||||||
AboutIconButton({super.key});
|
AboutIconButton();
|
||||||
|
|
||||||
final NavigationService _navigationService = locator<NavigationService>();
|
final NavigationService _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: const Icon(Icons.help),
|
icon: Icon(Icons.help),
|
||||||
|
color: whiteColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_navigationService.navigateTo(AboutView.routeName);
|
_navigationService.navigateTo(AboutView.routeName);
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ class CenteredErrorRow extends StatelessWidget {
|
||||||
final Function? retryCallback;
|
final Function? retryCallback;
|
||||||
final String? message;
|
final String? message;
|
||||||
|
|
||||||
const CenteredErrorRow(this.message, {super.key, this.retryCallback});
|
CenteredErrorRow(this.message, {this.retryCallback});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -20,10 +20,7 @@ class CenteredErrorRow extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(child: Center(child: Text(message!, style: TextStyle(color: redColor)))),
|
||||||
child: Center(
|
|
||||||
child: Text(message!,
|
|
||||||
style: const TextStyle(color: redColor)))),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(retryCallback != null
|
(retryCallback != null
|
||||||
|
@ -33,7 +30,7 @@ class CenteredErrorRow extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Center(
|
Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.refresh),
|
icon: Icon(Icons.refresh),
|
||||||
color: primaryAccentColor,
|
color: primaryAccentColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
retryCallback!();
|
retryCallback!();
|
||||||
|
|
|
@ -10,25 +10,18 @@ class LoginApiKeyHeaders extends StatelessWidget {
|
||||||
|
|
||||||
final String? validationMessage;
|
final String? validationMessage;
|
||||||
|
|
||||||
const LoginApiKeyHeaders(
|
LoginApiKeyHeaders({required this.uriController, required this.apiKeyController, this.validationMessage});
|
||||||
{super.key,
|
|
||||||
required this.uriController,
|
|
||||||
required this.apiKeyController,
|
|
||||||
this.validationMessage});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(children: <Widget>[
|
return Column(children: <Widget>[
|
||||||
validationMessage != null
|
this.validationMessage != null ? Text(validationMessage!, style: TextStyle(color: redColor)) : Container(),
|
||||||
? Text(validationMessage!, style: const TextStyle(color: redColor))
|
LoginTextField(uriController, translate('login.url_placeholder'), Icon(Icons.link),
|
||||||
: Container(),
|
|
||||||
LoginTextField(uriController, translate('login.url_placeholder'),
|
|
||||||
const Icon(Icons.link),
|
|
||||||
keyboardType: TextInputType.url),
|
keyboardType: TextInputType.url),
|
||||||
LoginTextField(
|
LoginTextField(
|
||||||
apiKeyController,
|
apiKeyController,
|
||||||
translate('login.apikey_placeholder'),
|
translate('login.apikey_placeholder'),
|
||||||
const Icon(Icons.vpn_key),
|
Icon(Icons.vpn_key),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -11,9 +11,8 @@ class LoginCredentialsHeaders extends StatelessWidget {
|
||||||
|
|
||||||
final String? validationMessage;
|
final String? validationMessage;
|
||||||
|
|
||||||
const LoginCredentialsHeaders(
|
LoginCredentialsHeaders(
|
||||||
{super.key,
|
{required this.uriController,
|
||||||
required this.uriController,
|
|
||||||
required this.usernameController,
|
required this.usernameController,
|
||||||
required this.passwordController,
|
required this.passwordController,
|
||||||
this.validationMessage});
|
this.validationMessage});
|
||||||
|
@ -21,17 +20,12 @@ class LoginCredentialsHeaders extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(children: <Widget>[
|
return Column(children: <Widget>[
|
||||||
validationMessage != null
|
this.validationMessage != null ? Text(validationMessage!, style: TextStyle(color: redColor)) : Container(),
|
||||||
? Text(validationMessage!, style: const TextStyle(color: redColor))
|
LoginTextField(uriController, translate('login.url_placeholder'), Icon(Icons.link),
|
||||||
: Container(),
|
|
||||||
LoginTextField(uriController, translate('login.url_placeholder'),
|
|
||||||
const Icon(Icons.link),
|
|
||||||
keyboardType: TextInputType.url),
|
keyboardType: TextInputType.url),
|
||||||
LoginTextField(usernameController,
|
LoginTextField(usernameController, translate('login.username_placeholder'), Icon(Icons.person),
|
||||||
translate('login.username_placeholder'), const Icon(Icons.person),
|
|
||||||
keyboardType: TextInputType.name),
|
keyboardType: TextInputType.name),
|
||||||
LoginTextField(passwordController,
|
LoginTextField(passwordController, translate('login.password_placeholder'), Icon(Icons.vpn_key),
|
||||||
translate('login.password_placeholder'), const Icon(Icons.vpn_key),
|
|
||||||
obscureText: true),
|
obscureText: true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../shared/app_colors.dart';
|
||||||
|
|
||||||
class LoginTextField extends StatelessWidget {
|
class LoginTextField extends StatelessWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final String placeHolder;
|
final String placeHolder;
|
||||||
|
@ -7,31 +9,29 @@ class LoginTextField extends StatelessWidget {
|
||||||
final bool obscureText;
|
final bool obscureText;
|
||||||
final Widget prefixIcon;
|
final Widget prefixIcon;
|
||||||
|
|
||||||
const LoginTextField(this.controller, this.placeHolder, this.prefixIcon,
|
LoginTextField(this.controller, this.placeHolder, this.prefixIcon,
|
||||||
{super.key,
|
{this.keyboardType = TextInputType.text, this.obscureText = false});
|
||||||
this.keyboardType = TextInputType.text,
|
|
||||||
this.obscureText = false});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
|
margin: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
|
||||||
height: 50.0,
|
height: 50.0,
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
|
decoration: BoxDecoration(color: whiteColor, borderRadius: BorderRadius.circular(10.0)),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
obscureText: obscureText,
|
obscureText: obscureText,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
onPressed: () => controller.clear(),
|
onPressed: () => controller.clear(),
|
||||||
icon: const Icon(Icons.clear),
|
icon: Icon(Icons.clear),
|
||||||
),
|
),
|
||||||
prefixIcon: prefixIcon,
|
prefixIcon: prefixIcon,
|
||||||
hintText: placeHolder,
|
hintText: placeHolder,
|
||||||
contentPadding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||||
border:
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
||||||
OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
|
||||||
),
|
),
|
||||||
controller: controller),
|
controller: controller),
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,18 +6,13 @@ class MyAppBar extends AppBar {
|
||||||
static final List<Widget> aboutEnabledWidgets = [AboutIconButton()];
|
static final List<Widget> aboutEnabledWidgets = [AboutIconButton()];
|
||||||
static final List<Widget> aboutDisabledWidgets = [];
|
static final List<Widget> aboutDisabledWidgets = [];
|
||||||
|
|
||||||
MyAppBar(
|
MyAppBar({Key? key, required Widget title, List<Widget>? actionWidgets, bool enableAbout = true})
|
||||||
{super.key,
|
: super(key: key, title: Row(children: <Widget>[title]), actions: _renderIconButtons(actionWidgets, enableAbout));
|
||||||
required Widget title,
|
|
||||||
List<Widget>? actionWidgets,
|
|
||||||
bool enableAbout = true})
|
|
||||||
: super(
|
|
||||||
title: Row(children: <Widget>[title]),
|
|
||||||
actions: _renderIconButtons(actionWidgets, enableAbout));
|
|
||||||
|
|
||||||
static List<Widget> _renderIconButtons(
|
static List<Widget> _renderIconButtons(List<Widget>? actionWidgets, bool aboutEnabled) {
|
||||||
List<Widget>? actionWidgets, bool aboutEnabled) {
|
if (actionWidgets == null) {
|
||||||
actionWidgets ??= [];
|
actionWidgets = [];
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> widgets = [...actionWidgets];
|
List<Widget> widgets = [...actionWidgets];
|
||||||
|
|
||||||
|
|
737
pubspec.lock
737
pubspec.lock
File diff suppressed because it is too large
Load diff
48
pubspec.yaml
48
pubspec.yaml
|
@ -11,47 +11,43 @@ description: A mobile client for FileBin.
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.6.4+22
|
version: 1.5.0+16
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.5.0 <4.0.0'
|
sdk: '>=2.18.5 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cupertino_icons: 1.0.8
|
cupertino_icons: 1.0.5
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_translate: 4.1.0
|
flutter_translate: 4.0.3
|
||||||
provider: 6.1.2
|
provider: 6.0.5
|
||||||
stacked: 3.4.3
|
stacked: 3.0.1
|
||||||
get_it: 7.7.0
|
get_it: 7.2.0
|
||||||
logger: 2.4.0
|
logger: 1.1.0
|
||||||
shared_preferences: 2.3.2
|
shared_preferences: 2.0.15
|
||||||
http: 1.2.2
|
http: 0.13.5
|
||||||
validators: 3.0.0
|
validators: 3.0.0
|
||||||
flutter_linkify: 6.0.0
|
flutter_linkify: 5.0.2
|
||||||
url_launcher: 6.3.1
|
url_launcher: 6.1.7
|
||||||
expandable: 5.0.1
|
expandable: 5.0.1
|
||||||
share_plus: 10.1.1
|
share_plus: 6.3.0
|
||||||
file_picker: 8.1.3
|
file_picker: 5.2.4
|
||||||
clipboard: 0.1.3
|
clipboard: 0.1.3
|
||||||
permission_handler: 11.3.1
|
receive_sharing_intent: 1.4.5
|
||||||
package_info_plus: 8.1.0
|
permission_handler: 10.2.0
|
||||||
json_annotation: 4.9.0
|
package_info_plus: 3.0.2
|
||||||
dynamic_color: 1.7.0
|
json_annotation: 4.7.0
|
||||||
intl: 0.19.0
|
dynamic_color: 1.5.4
|
||||||
path: 1.9.0
|
|
||||||
flutter_sharing_intent: 1.1.1
|
|
||||||
device_info_plus: 11.1.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: 2.4.13
|
build_runner: 2.3.2
|
||||||
built_value_generator: 8.9.2
|
built_value_generator: 8.4.2
|
||||||
json_serializable: 6.8.0
|
json_serializable: 6.5.4
|
||||||
flutter_lints: 5.0.0
|
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://www.dartlang.org/tools/pub/pubspec
|
# following page: https://www.dartlang.org/tools/pub/pubspec
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": [
|
|
||||||
"config:recommended",
|
|
||||||
":rebaseStalePrs",
|
|
||||||
":ignoreUnstable",
|
|
||||||
"group:monorepos",
|
|
||||||
"group:recommended"
|
|
||||||
],
|
|
||||||
"prConcurrentLimit": 0,
|
|
||||||
"schedule": [
|
|
||||||
"monthly"
|
|
||||||
],
|
|
||||||
"ignorePaths": [
|
|
||||||
"android/**",
|
|
||||||
"ios/**"
|
|
||||||
],
|
|
||||||
"ignoreDeps": [
|
|
||||||
"intl",
|
|
||||||
"path"
|
|
||||||
],
|
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchUpdateTypes": [
|
|
||||||
"minor"
|
|
||||||
],
|
|
||||||
"groupName": "all minor dependencies",
|
|
||||||
"groupSlug": "all-minor-deps"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchUpdateTypes": [
|
|
||||||
"patch"
|
|
||||||
],
|
|
||||||
"groupName": "all patch dependencies",
|
|
||||||
"groupSlug": "all-patch-deps"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
Reference in a new issue