Compare commits
212 commits
Author | SHA1 | Date | |
---|---|---|---|
ae2a6b8b32 | |||
dce3c916a2 | |||
58141fe646 | |||
dd5abc48f9 | |||
bf1f1e3295 | |||
e7463ffaef | |||
ae454abb3a | |||
34bcd2ee92 | |||
d2485a91cd | |||
e8c8808c0e | |||
b544003941 | |||
5668c15547 | |||
7f145f6960 | |||
d0dd798008 | |||
f16ee08079 | |||
a4a3c2e5dc | |||
a5e9597207 | |||
f460c68339 | |||
c7af6a874d | |||
e0973735d8 | |||
dc715e38d1 | |||
eb1d949af8 | |||
4a715aba48 | |||
c48c16d63d | |||
57db10e912 | |||
a4defff982 | |||
3ae2c37813 | |||
d9b33feac9 | |||
941b7c81ad | |||
5bc02638f7 | |||
477e557d31 | |||
c917e478a6 | |||
8b96610fa6 | |||
df40808bd7 | |||
dfc5772b93 | |||
3d6ae1a9ef | |||
e49dd86347 | |||
4cd6165d9b | |||
e661171fd2 | |||
c99df89cb0 | |||
03d61dcdd0 | |||
f2d1083620 | |||
b5694ae9c6 | |||
9dd84b20a9 | |||
8bb310fb40 | |||
47987b8dad | |||
3d68dcefab | |||
b51131de8f | |||
7e871bb4b1 | |||
88b9d81bc2 | |||
7c7befe071 | |||
2461a02f9d | |||
15104d7e40 | |||
733f09baab | |||
1758eeed18 | |||
c9764e780b | |||
3754d71dec | |||
8904d49eb2 | |||
0e7bc55bef | |||
089d07846d | |||
3f64f86acf | |||
7c91d8b6ca | |||
df1b038782 | |||
c9ef125abb | |||
5a8750b877 | |||
8c10e42c0f | |||
96d8cbea99 | |||
685b140135 | |||
f51e781a9f | |||
16c1a597fc | |||
71ac72a6cc | |||
0bc3ae1da8 | |||
fc5b8783d3 | |||
5492fed0b5 | |||
30810868b2 | |||
a889a6fbee | |||
36e5bcab1f | |||
3b06c2b80b | |||
e4cfa81672 | |||
2837054383 | |||
4deb9b92cb | |||
b0edda0432 | |||
886632957d | |||
264609dff3 | |||
d87796f47b | |||
42f40d52d2 | |||
292660a970 | |||
51e2c476c7 | |||
44cc8bac28 | |||
9b571fb1a4 | |||
d63ef288eb | |||
549b218922 | |||
f61c5a4a1b | |||
a06bbf6751 | |||
e980f1ae53 | |||
66a5f4e9af | |||
39b1465efc | |||
22d096ae43 | |||
08d935284d | |||
6b87873cc7 | |||
41987331b0 | |||
f19d23a757 | |||
67076779a3 | |||
6d302c4392 | |||
53cd587a42 | |||
769dd7e513 | |||
a3ee146e25 | |||
5e9947969e | |||
689c8b4408 | |||
7cee219b19 | |||
803cd11cc4 | |||
a99662af3c | |||
3d11fde4e9 | |||
43cb6472f2 | |||
67dafa615f | |||
fc0fe31b70 | |||
0e4dcbde22 | |||
65a0a294a6 | |||
548d02362b | |||
c50e9ec7ba | |||
e5518b4ade | |||
0b093302ec | |||
4114feb56e | |||
d8c9027509 | |||
e1075f5725 | |||
b49f10c875 | |||
494420cbd3 | |||
aedc7f2bbb | |||
9ac363070b | |||
580099856f | |||
d56d717de0 | |||
3cf96ad5ab | |||
26d0e0851b | |||
c772d950e2 | |||
0630035e45 | |||
3fb46fc766 | |||
53c65ce54f | |||
9a1db51f23 | |||
664b734544 | |||
0e69e904b4 | |||
09b608a168 | |||
091e344831 | |||
0f9e8b398f | |||
88d85257a7 | |||
06566d3d6e | |||
35bdd06f72 | |||
e59164e803 | |||
eec38b455c | |||
ee6230399a | |||
31d91bafc1 | |||
82f9ab5535 | |||
66493cff91 | |||
210c3e7aa4 | |||
d6b645112e | |||
713d8f57be | |||
bc777d4826 | |||
ad9a3a15f5 | |||
dbb4929939 | |||
adb55fd73b | |||
004133f0c0 | |||
67903d1331 | |||
0a0fbe039b | |||
6ef8a9daa0 | |||
47b45a9084 | |||
06dab63e05 | |||
9c331cf6d3 | |||
40451dbabc | |||
6bb4c177e2 | |||
81eb1af2bd | |||
3e7bc379f1 | |||
e9929bd3c3 | |||
9386661adb | |||
46a6df9f80 | |||
577f9cd42c | |||
d8d89167a9 | |||
6d75672add | |||
fc88d5c22f | |||
a2ee915463 | |||
6e5fa0eaa8 | |||
8c3bf06b87 | |||
5e9ac38ca0 | |||
a5dab51765 | |||
a65c7d9253 | |||
bac39aebdf | |||
b55e932204 | |||
f9a2bb0df7 | |||
a0789e6883 | |||
2e7778926a | |||
c2c8ffc5c5 | |||
b18d63ce1e | |||
06db805f82 | |||
3f927328f0 | |||
8d11584811 | |||
db9fef6798 | |||
6fedb8c661 | |||
d3e54eb1d5 | |||
874d734c80 | |||
a6cdd407c0 | |||
583520ea7c | |||
77fee6ae32 | |||
cd07b2fba2 | |||
011113d72b | |||
7ef179906d | |||
c8007e8415 | |||
a6851c5430 | |||
80818f5eda | |||
3625461b57 | |||
830deab2e1 | |||
15e1607072 | |||
71dcab97be | |||
3c06e3b29f | |||
2b1d8939d2 |
80 changed files with 1684 additions and 1261 deletions
21
.forgejo/workflows/build.yaml
Normal file
21
.forgejo/workflows/build.yaml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
on: [ push ]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: ghcr.io/cirruslabs/flutter:3.24.4
|
||||||
|
steps:
|
||||||
|
- name: Prepare requirements
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y nodejs npm git
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
flutter doctor
|
||||||
|
flutter pub get
|
||||||
|
flutter pub outdated
|
||||||
|
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
flutter analyze --no-pub --no-current-package lib/
|
||||||
|
flutter build apk --debug
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
*.class
|
*.class
|
||||||
*.lock
|
*.lock
|
||||||
|
!Gemfile.lock
|
||||||
!pubspec.lock
|
!pubspec.lock
|
||||||
*.log
|
*.log
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
48
.travis.yml
48
.travis.yml
|
@ -1,48 +0,0 @@
|
||||||
# based on https://gist.github.com/KNawm/9c7c7963e7f772d6d5172fe9da7ce79b
|
|
||||||
language: generic
|
|
||||||
dist: bionic
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- lib32stdc++6
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- FLUTTER_CHANNEL=stable
|
|
||||||
install:
|
|
||||||
- git clone https://github.com/flutter/flutter.git -b $FLUTTER_CHANNEL
|
|
||||||
- export PATH=$(pwd)/flutter/bin:$PATH
|
|
||||||
- export PATH=$(pwd)/flutter/bin/cache/dart-sdk/bin:$PATH
|
|
||||||
- flutter doctor -v
|
|
||||||
- flutter pub get
|
|
||||||
- flutter pub upgrade
|
|
||||||
- flutter packages pub run build_runner build --delete-conflicting-outputs
|
|
||||||
|
|
||||||
static_analysis: &static_analysis
|
|
||||||
name: "Static analysis"
|
|
||||||
script: flutter analyze --no-current-package $TRAVIS_BUILD_DIR/lib
|
|
||||||
|
|
||||||
#build: &build
|
|
||||||
# name: "Build APK"
|
|
||||||
# language: android
|
|
||||||
# android:
|
|
||||||
# components:
|
|
||||||
# - tools
|
|
||||||
# - tools
|
|
||||||
# - platform-tools
|
|
||||||
# - build-tools-28.0.3
|
|
||||||
# - android-28
|
|
||||||
# before_script:
|
|
||||||
# - export BUILD_NAME=$TRAVIS_TAG
|
|
||||||
# - export BUILD_NUMBER=$TRAVIS_BUILD_NUMBER
|
|
||||||
# script:
|
|
||||||
# - jdk_switcher use openjdk8
|
|
||||||
# - if [[ $TRAVIS_TAG == "" ]]; then flutter build apk; else flutter build apk --build-name $BUILD_NAME --build-number $BUILD_NUMBER; fi
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- <<: *static_analysis
|
|
||||||
# - stage: build
|
|
||||||
# <<: *build
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.pub-cache
|
|
45
CHANGELOG.md
45
CHANGELOG.md
|
@ -1,5 +1,50 @@
|
||||||
# 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
|
||||||
|
* Switched to Material You defaulting to blue swatch colors respecting dark mode
|
||||||
|
* Switched to Material You navigation bar and removed unsupported swipe navigation
|
||||||
|
* Increased target SDK to `33`
|
||||||
|
* Increased dart to `>= 2.18.5`
|
||||||
|
* Indicate configuration loading in profile view
|
||||||
|
* Switched linked git repository away from GitHub
|
||||||
|
* Updated internal dependencies
|
||||||
|
|
||||||
|
## 1.4.2+15
|
||||||
|
* Minor cleanup
|
||||||
|
* Added external drone CI
|
||||||
|
* Updated to Android embedding v2
|
||||||
|
* Updated to Gradle 7
|
||||||
|
* Upgraded internal dependencies to latest versions
|
||||||
|
|
||||||
## 1.4.1+14
|
## 1.4.1+14
|
||||||
* Fixed opening links
|
* Fixed opening links
|
||||||
|
|
||||||
|
|
|
@ -404,6 +404,14 @@ 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
|
||||||
|
|
45
README.md
45
README.md
|
@ -1,8 +1,14 @@
|
||||||
# README
|
# README
|
||||||
|
|
||||||
A mobile flutter app for [FileBin](https://github.com/Bluewind/filebin).
|
A mobile flutter app for [FileBin](https://git.server-speed.net/users/flo/filebin/).
|
||||||
|
|
||||||
Available on the [Play Store](https://play.google.com/store/apps/details?id=de.varakh.fbmobile).
|
Available on the [Play Store](https://play.google.com/store/apps/details?id=de.varakh.fbmobile) and
|
||||||
|
[IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.varakh.fbmobile/).
|
||||||
|
|
||||||
|
The main git repository is hosted at **[https://git.myservermanager.com/varakh/fbmobile](https://git.myservermanager.com/varakh/fbmobile)**.
|
||||||
|
Other repositories are mirrors and pull requests, issues, and planning are managed there.
|
||||||
|
|
||||||
|
Contributions are very welcome!
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
|
@ -29,13 +35,6 @@ This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
Start by installing dependencies and generating entities!
|
Start by installing dependencies and generating entities!
|
||||||
|
|
||||||
### Working versions for SDK
|
|
||||||
|
|
||||||
```
|
|
||||||
Flutter version 2.8.1
|
|
||||||
Dart version 2.15.1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
* Run `flutter packages pub get` in project root folder to get dependencies or open the `pubspec.yaml` and click on the buttons provided by the IDE plugins
|
* Run `flutter packages pub get` in project root folder to get dependencies or open the `pubspec.yaml` and click on the buttons provided by the IDE plugins
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ profiles. They're stored in a separate git repository and are encrypted.
|
||||||
|
|
||||||
You need access to the git repository in which those private files reside.
|
You need access to the git repository in which those private files reside.
|
||||||
|
|
||||||
#### Usage
|
#### Usage / doing the actual release
|
||||||
|
|
||||||
Go into the platform directory you want to build for, e.g. `ios/` or `android/` and then look into the
|
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
|
||||||
|
@ -106,13 +105,37 @@ following to build for Android `fastlane android build`.
|
||||||
|
|
||||||
##### Android
|
##### Android
|
||||||
|
|
||||||
Use `fastlane android beta` to build and upload a new beta version to the Play Store.
|
It's recommended you set up `fastlane` via `bundler` (you need this to be installed on your machine).
|
||||||
|
|
||||||
|
Go into the `android/` sub-directory of the project
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bundle config set --local path 'vendor/bundle'
|
||||||
|
bundle install
|
||||||
|
|
||||||
|
# update fastlane when needed
|
||||||
|
bundle update fastlane
|
||||||
|
|
||||||
|
# build only
|
||||||
|
bundle exec fastlane android build
|
||||||
|
|
||||||
|
# deploy (push BETA to app store)
|
||||||
|
bundle exec fastlane android beta
|
||||||
|
|
||||||
|
# deploy (push to app store)
|
||||||
|
bundle exec fastlane android deploy
|
||||||
|
|
||||||
|
# deploy (build signed fdroid large bundle [no target and abi split])
|
||||||
|
bundle exec fastlane android build_production_fdroid
|
||||||
|
```
|
||||||
|
|
||||||
##### iOS
|
##### 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:
|
||||||
|
|
30
analysis_options.yaml
Normal file
30
analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at
|
||||||
|
# https://dart-lang.github.io/linter/lints/index.html.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
library_private_types_in_public_api: false
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
2
android/.gitignore
vendored
2
android/.gitignore
vendored
|
@ -5,3 +5,5 @@ gradle-wrapper.jar
|
||||||
/gradlew.bat
|
/gradlew.bat
|
||||||
/local.properties
|
/local.properties
|
||||||
GeneratedPluginRegistrant.java
|
GeneratedPluginRegistrant.java
|
||||||
|
.bundle
|
||||||
|
vendor/
|
||||||
|
|
222
android/Gemfile.lock
Normal file
222
android/Gemfile.lock
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
CFPropertyList (3.0.7)
|
||||||
|
base64
|
||||||
|
nkf
|
||||||
|
rexml
|
||||||
|
addressable (2.8.7)
|
||||||
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
|
artifactory (3.0.17)
|
||||||
|
atomos (0.1.3)
|
||||||
|
aws-eventstream (1.3.0)
|
||||||
|
aws-partitions (1.1000.0)
|
||||||
|
aws-sdk-core (3.211.0)
|
||||||
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
|
aws-sigv4 (~> 1.9)
|
||||||
|
jmespath (~> 1, >= 1.6.1)
|
||||||
|
aws-sdk-kms (1.95.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
|
aws-sigv4 (~> 1.5)
|
||||||
|
aws-sdk-s3 (1.169.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
|
aws-sdk-kms (~> 1)
|
||||||
|
aws-sigv4 (~> 1.5)
|
||||||
|
aws-sigv4 (1.10.1)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
babosa (1.0.4)
|
||||||
|
base64 (0.2.0)
|
||||||
|
claide (1.1.0)
|
||||||
|
colored (1.2)
|
||||||
|
colored2 (3.1.2)
|
||||||
|
commander (4.6.0)
|
||||||
|
highline (~> 2.0.0)
|
||||||
|
declarative (0.0.20)
|
||||||
|
digest-crc (0.6.5)
|
||||||
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
|
domain_name (0.6.20240107)
|
||||||
|
dotenv (2.8.1)
|
||||||
|
emoji_regex (3.2.3)
|
||||||
|
excon (0.112.0)
|
||||||
|
faraday (1.10.4)
|
||||||
|
faraday-em_http (~> 1.0)
|
||||||
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0)
|
||||||
|
faraday-multipart (~> 1.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.0)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
|
faraday-retry (~> 1.0)
|
||||||
|
ruby2_keywords (>= 0.0.4)
|
||||||
|
faraday-cookie_jar (0.0.7)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
|
http-cookie (~> 1.0.0)
|
||||||
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.0)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-httpclient (1.0.1)
|
||||||
|
faraday-multipart (1.0.4)
|
||||||
|
multipart-post (~> 2)
|
||||||
|
faraday-net_http (1.0.2)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday-retry (1.0.3)
|
||||||
|
faraday_middleware (1.2.1)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
fastimage (2.3.1)
|
||||||
|
fastlane (2.225.0)
|
||||||
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
|
addressable (>= 2.8, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
|
aws-sdk-s3 (~> 1.0)
|
||||||
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
|
colored (~> 1.2)
|
||||||
|
commander (~> 4.6)
|
||||||
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
|
faraday_middleware (~> 1.0)
|
||||||
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
|
fastlane-sirp (>= 1.0.0)
|
||||||
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
|
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||||
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
|
http-cookie (~> 1.0.5)
|
||||||
|
json (< 3.0.0)
|
||||||
|
jwt (>= 2.1.0, < 3)
|
||||||
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
|
naturally (~> 2.2)
|
||||||
|
optparse (>= 0.1.1, < 1.0.0)
|
||||||
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
|
security (= 0.1.5)
|
||||||
|
simctl (~> 1.6.3)
|
||||||
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
|
terminal-table (~> 3)
|
||||||
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
|
word_wrap (~> 1.0.0)
|
||||||
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
|
xcpretty (~> 0.3.0)
|
||||||
|
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||||
|
fastlane-sirp (1.0.0)
|
||||||
|
sysrandom (~> 1.0)
|
||||||
|
gh_inspector (1.1.3)
|
||||||
|
google-apis-androidpublisher_v3 (0.54.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-apis-core (0.11.3)
|
||||||
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
representable (~> 3.0)
|
||||||
|
retriable (>= 2.0, < 4.a)
|
||||||
|
rexml
|
||||||
|
google-apis-iamcredentials_v1 (0.17.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-apis-playcustomapp_v1 (0.13.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.31.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-cloud-core (1.7.1)
|
||||||
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
|
google-cloud-errors (~> 1.0)
|
||||||
|
google-cloud-env (1.6.0)
|
||||||
|
faraday (>= 0.17.3, < 3.0)
|
||||||
|
google-cloud-errors (1.4.0)
|
||||||
|
google-cloud-storage (1.47.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
digest-crc (~> 0.4)
|
||||||
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
|
google-apis-storage_v1 (~> 0.31.0)
|
||||||
|
google-cloud-core (~> 1.6)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
googleauth (1.8.1)
|
||||||
|
faraday (>= 0.17.3, < 3.a)
|
||||||
|
jwt (>= 1.4, < 3.0)
|
||||||
|
multi_json (~> 1.11)
|
||||||
|
os (>= 0.9, < 2.0)
|
||||||
|
signet (>= 0.16, < 2.a)
|
||||||
|
highline (2.0.3)
|
||||||
|
http-cookie (1.0.7)
|
||||||
|
domain_name (~> 0.5)
|
||||||
|
httpclient (2.8.3)
|
||||||
|
jmespath (1.6.2)
|
||||||
|
json (2.7.5)
|
||||||
|
jwt (2.9.3)
|
||||||
|
base64
|
||||||
|
mini_magick (4.13.2)
|
||||||
|
mini_mime (1.1.5)
|
||||||
|
multi_json (1.15.0)
|
||||||
|
multipart-post (2.4.1)
|
||||||
|
nanaimo (0.4.0)
|
||||||
|
naturally (2.2.1)
|
||||||
|
nkf (0.2.0)
|
||||||
|
optparse (0.5.0)
|
||||||
|
os (1.1.4)
|
||||||
|
plist (3.7.1)
|
||||||
|
public_suffix (6.0.1)
|
||||||
|
rake (13.2.1)
|
||||||
|
representable (3.2.0)
|
||||||
|
declarative (< 0.1.0)
|
||||||
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
|
uber (< 0.2.0)
|
||||||
|
retriable (3.1.2)
|
||||||
|
rexml (3.3.9)
|
||||||
|
rouge (2.0.7)
|
||||||
|
ruby2_keywords (0.0.5)
|
||||||
|
rubyzip (2.3.2)
|
||||||
|
security (0.1.5)
|
||||||
|
signet (0.19.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
faraday (>= 0.17.5, < 3.a)
|
||||||
|
jwt (>= 1.5, < 3.0)
|
||||||
|
multi_json (~> 1.10)
|
||||||
|
simctl (1.6.10)
|
||||||
|
CFPropertyList
|
||||||
|
naturally
|
||||||
|
sysrandom (1.0.5)
|
||||||
|
terminal-notifier (2.0.0)
|
||||||
|
terminal-table (3.0.2)
|
||||||
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
|
trailblazer-option (0.1.2)
|
||||||
|
tty-cursor (0.7.1)
|
||||||
|
tty-screen (0.8.2)
|
||||||
|
tty-spinner (0.9.3)
|
||||||
|
tty-cursor (~> 0.7)
|
||||||
|
uber (0.1.0)
|
||||||
|
unicode-display_width (2.6.0)
|
||||||
|
word_wrap (1.0.0)
|
||||||
|
xcodeproj (1.27.0)
|
||||||
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
|
atomos (~> 0.1.3)
|
||||||
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
nanaimo (~> 0.4.0)
|
||||||
|
rexml (>= 3.3.6, < 4.0)
|
||||||
|
xcpretty (0.3.0)
|
||||||
|
rouge (~> 2.0.7)
|
||||||
|
xcpretty-travis-formatter (1.0.1)
|
||||||
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
x86_64-linux
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
fastlane
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.5.16
|
|
@ -1,3 +1,9 @@
|
||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
id "kotlin-android"
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
def localProperties = new Properties()
|
def localProperties = new Properties()
|
||||||
def localPropertiesFile = rootProject.file('local.properties')
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
if (localPropertiesFile.exists()) {
|
if (localPropertiesFile.exists()) {
|
||||||
|
@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
|
||||||
if (flutterRoot == null) {
|
|
||||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
flutterVersionCode = '1'
|
flutterVersionCode = '1'
|
||||||
|
@ -21,9 +22,6 @@ 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()) {
|
||||||
|
@ -31,7 +29,9 @@ if (keystorePropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 34
|
||||||
|
|
||||||
|
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 16
|
minSdkVersion 30
|
||||||
targetSdkVersion 30
|
targetSdkVersion 34
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
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" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
additional functionality it is fine to subclass or reimplement
|
additional functionality it is fine to subclass or reimplement
|
||||||
FlutterApplication and put your custom class here. -->
|
FlutterApplication and put your custom class here. -->
|
||||||
<application
|
<application
|
||||||
android:name="io.flutter.app.FlutterApplication"
|
android:name="${applicationName}"
|
||||||
android:label="FileBin"
|
android:label="FileBin"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
@ -20,36 +21,31 @@
|
||||||
<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" />
|
<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="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="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" />
|
||||||
|
@ -69,7 +65,10 @@
|
||||||
</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" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
package de.varakh.fbmobile;
|
package de.varakh.fbmobile;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity;
|
import io.flutter.embedding.android.FlutterActivity;
|
||||||
import io.flutter.embedding.engine.FlutterEngine;
|
|
||||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
|
||||||
|
|
||||||
public class MainActivity extends FlutterActivity {
|
public class MainActivity extends FlutterActivity {
|
||||||
@Override
|
|
||||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
|
||||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
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" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
|
|
|
@ -1,16 +1,3 @@
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
// TODO switch to 4.0.1 again: https://github.com/flutter/flutter/issues/58479
|
|
||||||
// TODO switch to 4.0.1 again: https://github.com/miguelpruivo/flutter_file_picker/issues/545
|
|
||||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
@ -26,6 +13,6 @@ subprojects {
|
||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
tasks.register("clean", Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,11 @@ 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,6 +31,14 @@ 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
|
||||||
|
|
9
android/fastlane/buildAndroidProductionFdroid.sh
Executable file
9
android/fastlane/buildAndroidProductionFdroid.sh
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
cd ../../;
|
||||||
|
flutter clean && \
|
||||||
|
flutter pub get &&
|
||||||
|
flutter packages pub run build_runner build --delete-conflicting-outputs;
|
||||||
|
|
||||||
|
flutter build apk --release;
|
||||||
|
flutter build apk --split-per-abi --release;
|
|
@ -1,4 +1,6 @@
|
||||||
|
agpVersion=8.7.2
|
||||||
|
kotlinVersion=1.7.10
|
||||||
|
|
||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
android.enableR8=true
|
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#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-6.1.1-all.zip
|
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
include ':app'
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}()
|
||||||
|
|
||||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
def plugins = new Properties()
|
repositories {
|
||||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
google()
|
||||||
if (pluginsFile.exists()) {
|
mavenCentral()
|
||||||
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.each { name, path ->
|
plugins {
|
||||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
include ":$name"
|
id "com.android.application" version "${agpVersion}" apply false
|
||||||
project(":$name").projectDir = pluginDirectory
|
id "org.jetbrains.kotlin.android" version "${kotlinVersion}" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
include ":app"
|
||||||
|
|
|
@ -18,12 +18,6 @@
|
||||||
"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)...",
|
||||||
|
@ -45,7 +39,6 @@
|
||||||
"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."
|
||||||
|
@ -99,12 +92,13 @@
|
||||||
"faq_headline": "F.A.Q",
|
"faq_headline": "F.A.Q",
|
||||||
"faq": "- How do I login?\nInsert your instance URL and valid credentials you also use in the web interface of FileBin.\n\n- Why is storage permission required?\nIt's not required, but highly advised to grant it. Otherwise sharing files with the app won't work correctly and you might think that sharing has no effect.\n\n- When I am logged out, sharing files via share with the app won't list all files I selected after I login.\nPlease login before you start using the app. Account information are persisted. You only need to do it once.",
|
"faq": "- How do I login?\nInsert your instance URL and valid credentials you also use in the web interface of FileBin.\n\n- Why is storage permission required?\nIt's not required, but highly advised to grant it. Otherwise sharing files with the app won't work correctly and you might think that sharing has no effect.\n\n- When I am logged out, sharing files via share with the app won't list all files I selected after I login.\nPlease login before you start using the app. Account information are persisted. You only need to do it once.",
|
||||||
"contact_us": "Feedback? Issues?",
|
"contact_us": "Feedback? Issues?",
|
||||||
"website": "https://github.com/Bluewind/filebin and https://github.com/v4rakh/fbmobile"
|
"website": "https://git.server-speed.net/users/flo/filebin and https://git.myservermanager.com/varakh/fbmobile"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"instance": "Instance",
|
"instance": "Instance",
|
||||||
"connection": "{url}",
|
"connection": "{url}",
|
||||||
"show_config": "Show configuration",
|
"show_config": "Show configuration",
|
||||||
|
"show_config_loading": "Loading configuration...",
|
||||||
"shown_config": {
|
"shown_config": {
|
||||||
"title": "Configuration",
|
"title": "Configuration",
|
||||||
"description": "Upload max size: {uploadMaxSize}\n\nMax files per request: {maxFilesPerRequest}\n\nMax inputs vars: {maxInputVars}\n\nRequest max size: {requestMaxSize}",
|
"description": "Upload max size: {uploadMaxSize}\n\nMax files per request: {maxFilesPerRequest}\n\nMax inputs vars: {maxInputVars}\n\nRequest max size: {requestMaxSize}",
|
||||||
|
@ -132,14 +126,6 @@
|
||||||
"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 2) on create share extension (https://pub.dev/packages/receive_sharing_intent)
|
// TODO follow steps on create share extension (https://pub.dev/packages/flutter_sharing_intent)
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<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>
|
||||||
|
|
74
lib/app.dart
74
lib/app.dart
|
@ -1,9 +1,10 @@
|
||||||
|
import 'package:dynamic_color/dynamic_color.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:intl/date_symbol_data_local.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'core/enums/refresh_event.dart';
|
import 'core/enums/refresh_event.dart';
|
||||||
import 'core/enums/swipe_event.dart';
|
|
||||||
import 'core/manager/dialog_manager.dart';
|
import 'core/manager/dialog_manager.dart';
|
||||||
import 'core/manager/lifecycle_manager.dart';
|
import 'core/manager/lifecycle_manager.dart';
|
||||||
import 'core/models/session.dart';
|
import 'core/models/session.dart';
|
||||||
|
@ -11,49 +12,60 @@ import 'core/services/dialog_service.dart';
|
||||||
import 'core/services/navigation_service.dart';
|
import 'core/services/navigation_service.dart';
|
||||||
import 'core/services/refresh_service.dart';
|
import 'core/services/refresh_service.dart';
|
||||||
import 'core/services/session_service.dart';
|
import 'core/services/session_service.dart';
|
||||||
import 'core/services/swipe_service.dart';
|
|
||||||
import 'locator.dart';
|
import 'locator.dart';
|
||||||
import 'ui/app_router.dart';
|
import 'ui/app_router.dart';
|
||||||
import 'ui/shared/app_colors.dart';
|
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(
|
||||||
|
primarySwatch: myColor, brightness: Brightness.light);
|
||||||
|
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(
|
||||||
|
primarySwatch: myColor, brightness: Brightness.dark);
|
||||||
|
|
||||||
|
MyApp({super.key}) {
|
||||||
|
initializeDateFormatting('en');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var localizationDelegate = LocalizedApp.of(context).delegate;
|
var localizationDelegate = LocalizedApp.of(context).delegate;
|
||||||
|
|
||||||
return LocalizationProvider(
|
return LocalizationProvider(
|
||||||
state: LocalizationProvider.of(context).state,
|
state: LocalizationProvider.of(context).state,
|
||||||
child: StreamProvider<SwipeEvent?>(
|
child: StreamProvider<RefreshEvent?>(
|
||||||
initialData: null,
|
initialData: null,
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
locator<SwipeService>().swipeEventController.stream,
|
locator<RefreshService>().refreshEventController.stream,
|
||||||
child: StreamProvider<RefreshEvent?>(
|
child: StreamProvider<Session?>(
|
||||||
initialData: null,
|
initialData: Session.initial(),
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
locator<RefreshService>().refreshEventController.stream,
|
locator<SessionService>().sessionController.stream,
|
||||||
child: StreamProvider<Session?>(
|
child: LifeCycleManager(child: DynamicColorBuilder(
|
||||||
initialData: Session.initial(),
|
builder: (lightColorScheme, darkColorScheme) {
|
||||||
create: (context) =>
|
return MaterialApp(
|
||||||
locator<SessionService>().sessionController.stream,
|
debugShowCheckedModeBanner: false,
|
||||||
child: LifeCycleManager(
|
title: translate('app.title'),
|
||||||
child: MaterialApp(
|
builder: (context, child) => Navigator(
|
||||||
title: translate('app.title'),
|
key: locator<DialogService>().dialogNavigationKey,
|
||||||
builder: (context, child) => Navigator(
|
onGenerateRoute: (settings) => MaterialPageRoute(
|
||||||
key: locator<DialogService>().dialogNavigationKey,
|
builder: (context) => DialogManager(child: child)),
|
||||||
onGenerateRoute: (settings) => MaterialPageRoute(
|
),
|
||||||
builder: (context) => DialogManager(child: child)),
|
theme: ThemeData(
|
||||||
),
|
useMaterial3: true,
|
||||||
theme: ThemeData(
|
brightness: Brightness.light,
|
||||||
brightness: Brightness.light,
|
colorScheme:
|
||||||
primarySwatch: primaryAccentColor as MaterialColor?,
|
lightColorScheme ?? _defaultLightColorScheme),
|
||||||
primaryColor: primaryAccentColor),
|
darkTheme: ThemeData(
|
||||||
onGenerateRoute: AppRouter.generateRoute,
|
useMaterial3: true,
|
||||||
navigatorKey: locator<NavigationService>().navigationKey,
|
colorScheme: darkColorScheme ?? _defaultDarkColorScheme),
|
||||||
home: StartUpView(),
|
onGenerateRoute: AppRouter.generateRoute,
|
||||||
supportedLocales: localizationDelegate.supportedLocales,
|
navigatorKey: locator<NavigationService>().navigationKey,
|
||||||
locale: localizationDelegate.currentLocale,
|
home: const StartUpView(),
|
||||||
)),
|
supportedLocales: localizationDelegate.supportedLocales,
|
||||||
))));
|
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
|
||||||
GENERAL_ERROR,
|
generalError,
|
||||||
|
|
||||||
/// Errors related to connections
|
/// Errors related to connections
|
||||||
SOCKET_ERROR,
|
socketError,
|
||||||
SOCKET_TIMEOUT,
|
socketTimeout,
|
||||||
|
|
||||||
/// A REST error (response code wasn't 200 or 204)
|
/// A REST error (response code wasn't 200 or 204)
|
||||||
REST_ERROR,
|
restError,
|
||||||
|
|
||||||
/// Custom errors
|
/// Custom errors
|
||||||
INVALID_API_KEY
|
invalidApiKey
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
enum RefreshEvent { RefreshHistory }
|
enum RefreshEvent { refreshHistory }
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
enum SwipeEvent { Start, Left, Right, End }
|
|
|
@ -1 +1 @@
|
||||||
enum ViewState { Idle, Busy }
|
enum ViewState { idle, busy }
|
||||||
|
|
|
@ -5,9 +5,10 @@ class RestServiceException extends ServiceException {
|
||||||
final int statusCode;
|
final int statusCode;
|
||||||
final dynamic responseBody;
|
final dynamic responseBody;
|
||||||
|
|
||||||
RestServiceException(this.statusCode, {this.responseBody, String? message})
|
RestServiceException(this.statusCode, {this.responseBody, super.message = null})
|
||||||
: super(code: ErrorCode.REST_ERROR, message: message);
|
: super(code: ErrorCode.restError);
|
||||||
|
|
||||||
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "$code $statusCode $message";
|
return "$code $statusCode $message";
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@ class ServiceException implements Exception {
|
||||||
final ErrorCode code;
|
final ErrorCode code;
|
||||||
final String? message;
|
final String? message;
|
||||||
|
|
||||||
ServiceException({this.code = ErrorCode.GENERAL_ERROR, this.message = ''});
|
ServiceException({this.code = ErrorCode.generalError, this.message = ''});
|
||||||
|
|
||||||
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "$code: $message";
|
return "$code: $message";
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,14 @@ import '../services/dialog_service.dart';
|
||||||
class DialogManager extends StatefulWidget {
|
class DialogManager extends StatefulWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
|
|
||||||
DialogManager({Key? key, this.child}) : super(key: key);
|
const DialogManager({super.key, this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
_DialogManagerState createState() => _DialogManagerState();
|
_DialogManagerState createState() => _DialogManagerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DialogManagerState extends State<DialogManager> {
|
class _DialogManagerState extends State<DialogManager> {
|
||||||
DialogService _dialogService = locator<DialogService>();
|
final DialogService _dialogService = locator<DialogService>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
|
@ -11,8 +11,9 @@ import '../util/logger.dart';
|
||||||
class LifeCycleManager extends StatefulWidget {
|
class LifeCycleManager extends StatefulWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
|
|
||||||
LifeCycleManager({Key? key, this.child}) : super(key: key);
|
const LifeCycleManager({super.key, this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
_LifeCycleManagerState createState() => _LifeCycleManagerState();
|
_LifeCycleManagerState createState() => _LifeCycleManagerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,13 +34,13 @@ class _LifeCycleManagerState extends State<LifeCycleManager>
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance!.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
WidgetsBinding.instance!.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -47,12 +48,12 @@ class _LifeCycleManagerState extends State<LifeCycleManager>
|
||||||
logger.d('LifeCycle event ${state.toString()}');
|
logger.d('LifeCycle event ${state.toString()}');
|
||||||
super.didChangeAppLifecycleState(state);
|
super.didChangeAppLifecycleState(state);
|
||||||
|
|
||||||
servicesToManage.forEach((service) {
|
for (var service in servicesToManage) {
|
||||||
if (state == AppLifecycleState.resumed) {
|
if (state == AppLifecycleState.resumed) {
|
||||||
service.start();
|
service.start();
|
||||||
} else {
|
} else {
|
||||||
service.stop();
|
service.stop();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import '../models/rest/uploaded_response.dart';
|
||||||
import '../services/api.dart';
|
import '../services/api.dart';
|
||||||
|
|
||||||
class FileRepository {
|
class FileRepository {
|
||||||
Api _api = locator<Api>();
|
final 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');
|
||||||
|
@ -41,12 +41,12 @@ class FileRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UploadedMultiResponse> postCreateMultiPaste(List<String> ids) async {
|
Future<UploadedMultiResponse> postCreateMultiPaste(List<String> ids) async {
|
||||||
Map<String, String> multiPasteIds = Map();
|
Map<String, String> multiPasteIds = {};
|
||||||
|
|
||||||
ids.forEach((element) {
|
for (var element in ids) {
|
||||||
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);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import '../models/rest/create_apikey_response.dart';
|
||||||
import '../services/api.dart';
|
import '../services/api.dart';
|
||||||
|
|
||||||
class UserRepository {
|
class UserRepository {
|
||||||
Api _api = locator<Api>();
|
final Api _api = locator<Api>();
|
||||||
|
|
||||||
Future<CreateApiKeyResponse> postApiKey(String url, String username,
|
Future<CreateApiKeyResponse> postApiKey(String url, String username,
|
||||||
String password, String accessLevel, String comment) async {
|
String password, String accessLevel, String comment) async {
|
||||||
|
|
|
@ -24,11 +24,11 @@ class Api implements ApiErrorConverter {
|
||||||
String _url = "";
|
String _url = "";
|
||||||
String _apiKey = "";
|
String _apiKey = "";
|
||||||
|
|
||||||
Map<String, String> _headers = {
|
final Map<String, String> _headers = {
|
||||||
"Content-Type": _applicationJson,
|
"Content-Type": _applicationJson,
|
||||||
"Accept": _applicationJson
|
"Accept": _applicationJson
|
||||||
};
|
};
|
||||||
Duration _timeout = Duration(seconds: Constants.apiRequestTimeoutLimit);
|
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 {
|
||||||
|
@ -41,10 +41,10 @@ class Api implements ApiErrorConverter {
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,13 +67,13 @@ class Api implements ApiErrorConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files != null && files.isNotEmpty) {
|
if (files != null && files.isNotEmpty) {
|
||||||
files.forEach((element) async {
|
for (var element in files) {
|
||||||
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.length > 0) {
|
if (additionalFiles != null && additionalFiles.isNotEmpty) {
|
||||||
List<String> keys = additionalFiles.keys.toList();
|
List<String> keys = additionalFiles.keys.toList();
|
||||||
additionalFiles.forEach((key, value) {
|
additionalFiles.forEach((key, value) {
|
||||||
var index = files != null
|
var index = files != null
|
||||||
|
@ -92,10 +92,10 @@ class Api implements ApiErrorConverter {
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,12 +132,12 @@ class Api implements ApiErrorConverter {
|
||||||
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 new RestServiceException(response.statusCode,
|
throw RestServiceException(response.statusCode,
|
||||||
responseBody: parsedBody);
|
responseBody: parsedBody);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RestServiceException(response.statusCode);
|
throw RestServiceException(response.statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ import '../datamodels/dialog_request.dart';
|
||||||
import '../datamodels/dialog_response.dart';
|
import '../datamodels/dialog_response.dart';
|
||||||
|
|
||||||
class DialogService {
|
class DialogService {
|
||||||
GlobalKey<NavigatorState> _dialogNavigationKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> _dialogNavigationKey =
|
||||||
|
GlobalKey<NavigatorState>();
|
||||||
late Function(DialogRequest) _showDialogListener;
|
late Function(DialogRequest) _showDialogListener;
|
||||||
Completer<DialogResponse>? _dialogCompleter;
|
Completer<DialogResponse>? _dialogCompleter;
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,10 @@ class LinkService {
|
||||||
final DialogService _dialogService = locator<DialogService>();
|
final DialogService _dialogService = locator<DialogService>();
|
||||||
|
|
||||||
Future open(String link) async {
|
Future open(String link) async {
|
||||||
if (await canLaunch(link)) {
|
Uri uri = Uri.parse(link);
|
||||||
await launch(link);
|
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
} else {
|
} else {
|
||||||
_logger.e('Could not launch link $link');
|
_logger.e('Could not launch link $link');
|
||||||
_dialogService.showDialog(
|
_dialogService.showDialog(
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:logger/logger.dart';
|
||||||
import '../util/logger.dart';
|
import '../util/logger.dart';
|
||||||
|
|
||||||
class NavigationService {
|
class NavigationService {
|
||||||
GlobalKey<NavigatorState> _navigationKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> _navigationKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
GlobalKey<NavigatorState> get navigationKey => _navigationKey;
|
GlobalKey<NavigatorState> get navigationKey => _navigationKey;
|
||||||
|
|
||||||
|
@ -17,11 +17,13 @@ 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!.pushNamed(routeName, arguments: arguments);
|
return _navigationKey.currentState!
|
||||||
|
.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!.pushReplacementNamed(routeName, arguments: arguments);
|
return _navigationKey.currentState!
|
||||||
|
.pushReplacementNamed(routeName, arguments: arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,117 +1,109 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package: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;
|
|
||||||
|
|
||||||
PermissionService() {
|
bool _deviceInformationInitialized = false;
|
||||||
_devicePermissionDialogActive = true;
|
bool _useStoragePermission = true;
|
||||||
|
|
||||||
Permission.storage.request().then((status) {
|
PermissionService();
|
||||||
_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_ownPermissionDialogActive) {
|
bool allGranted = false;
|
||||||
_logger.d('Own permission dialog already active, skipping');
|
bool anyPermanentlyDenied = false;
|
||||||
return;
|
|
||||||
|
// Since Android compileSdk >= 33, "storage" is deprecated
|
||||||
|
// Instead, request access to all of
|
||||||
|
// - Permission.photos
|
||||||
|
// - Permission.videos
|
||||||
|
// - Permission.audio
|
||||||
|
//
|
||||||
|
// For iOS and Android < 33, keep using "storage"
|
||||||
|
if (_useStoragePermission) {
|
||||||
|
PermissionStatus storagePermission = await Permission.storage.status;
|
||||||
|
allGranted = PermissionStatus.granted == storagePermission;
|
||||||
|
anyPermanentlyDenied =
|
||||||
|
PermissionStatus.permanentlyDenied == storagePermission;
|
||||||
|
} else {
|
||||||
|
PermissionStatus photosPermission = await Permission.photos.status;
|
||||||
|
PermissionStatus videosPermission = await Permission.videos.status;
|
||||||
|
PermissionStatus audioPermission = await Permission.audio.status;
|
||||||
|
|
||||||
|
allGranted = PermissionStatus.granted == photosPermission &&
|
||||||
|
PermissionStatus.granted == videosPermission &&
|
||||||
|
PermissionStatus.granted == audioPermission;
|
||||||
|
anyPermanentlyDenied =
|
||||||
|
PermissionStatus.permanentlyDenied == photosPermission ||
|
||||||
|
PermissionStatus.permanentlyDenied == videosPermission ||
|
||||||
|
PermissionStatus.permanentlyDenied == audioPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ignoredDialog =
|
// show warning to user to manually handle, don't enforce it over and over again
|
||||||
await _storageService.hasStoragePermissionDialogIgnored();
|
if (anyPermanentlyDenied) {
|
||||||
|
_logger.w(
|
||||||
if (ignoredDialog) {
|
"At least one required permission has been denied permanently, stopping service");
|
||||||
_logger.d('Permanently ignored permission request, skipping');
|
|
||||||
stop();
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_permissionStatus = await Permission.storage.status;
|
// all good, stop the permission service
|
||||||
if (_permissionStatus != PermissionStatus.granted) {
|
if (allGranted) {
|
||||||
if (_permissionStatus == PermissionStatus.permanentlyDenied) {
|
_logger.d("All permissions have been granted, stopping service");
|
||||||
await _storageService.storeStoragePermissionDialogIgnored();
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ownPermissionDialogActive = true;
|
// not all have been granted, show OS dialog
|
||||||
DialogResponse response = await _dialogService.showConfirmationDialog(
|
_logger.d(
|
||||||
title: translate('permission_service.dialog.title'),
|
"Not all permissions have been granted yet, initializing permission dialog");
|
||||||
description: translate('permission_service.dialog.description'),
|
_devicePermissionDialogActive = true;
|
||||||
buttonTitleAccept: translate('permission_service.dialog.grant'),
|
|
||||||
buttonTitleDeny: translate('permission_service.dialog.ignore'));
|
|
||||||
|
|
||||||
if (!response.confirmed!) {
|
if (_useStoragePermission) {
|
||||||
await _storageService.storeStoragePermissionDialogIgnored();
|
await [Permission.storage].request().whenComplete(() {
|
||||||
} else {
|
_logger.d('Device request permission finished');
|
||||||
_devicePermissionDialogActive = true;
|
_devicePermissionDialogActive = false;
|
||||||
Permission.storage.request().then((status) async {
|
});
|
||||||
if (PermissionStatus.permanentlyDenied == status) {
|
|
||||||
await _storageService.storeStoragePermissionDialogIgnored();
|
|
||||||
}
|
|
||||||
}).whenComplete(() {
|
|
||||||
_logger.d('Device request permission finished');
|
|
||||||
_devicePermissionDialogActive = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_ownPermissionDialogActive = false;
|
|
||||||
} else {
|
} else {
|
||||||
await _storageService.storeStoragePermissionDialogIgnored();
|
await [Permission.photos, Permission.videos, Permission.audio]
|
||||||
|
.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 = Timer.periodic(
|
||||||
Duration(milliseconds: Constants.mediaPermissionCheckInterval),
|
const 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');
|
||||||
|
@ -124,6 +116,29 @@ 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,7 +3,8 @@ import 'dart:async';
|
||||||
import '../enums/refresh_event.dart';
|
import '../enums/refresh_event.dart';
|
||||||
|
|
||||||
class RefreshService {
|
class RefreshService {
|
||||||
StreamController<RefreshEvent> refreshEventController = StreamController<RefreshEvent>.broadcast();
|
StreamController<RefreshEvent> refreshEventController =
|
||||||
|
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 = new Session(url: url, apiKey: apiKey);
|
var session = 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,45 +5,36 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/session.dart';
|
import '../models/session.dart';
|
||||||
|
|
||||||
class StorageService {
|
class StorageService {
|
||||||
static const _SESSION_KEY = 'session';
|
static const _sessionKey = 'session';
|
||||||
static const _LAST_URL_KEY = 'last_url';
|
static const _lastUrlKey = 'last_url';
|
||||||
static const _STORAGE_PERMISSION_DIALOG_IGNORED = 'storage_permission_ignored';
|
|
||||||
|
|
||||||
Future<bool> storeLastUrl(String url) {
|
Future<bool> storeLastUrl(String url) {
|
||||||
return _store(_LAST_URL_KEY, url);
|
return _store(_lastUrlKey, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> retrieveLastUrl() async {
|
Future<String?> retrieveLastUrl() async {
|
||||||
return await _retrieve(_LAST_URL_KEY);
|
return await _retrieve(_lastUrlKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasLastUrl() async {
|
Future<bool> hasLastUrl() async {
|
||||||
return await _exists(_LAST_URL_KEY);
|
return await _exists(_lastUrlKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> storeSession(Session session) {
|
Future<bool> storeSession(Session session) {
|
||||||
return _store(_SESSION_KEY, json.encode(session));
|
return _store(_sessionKey, json.encode(session));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Session> retrieveSession() async {
|
Future<Session> retrieveSession() async {
|
||||||
var retrieve = await _retrieve(_SESSION_KEY);
|
var retrieve = await _retrieve(_sessionKey);
|
||||||
return Session.fromJson(json.decode(retrieve!));
|
return Session.fromJson(json.decode(retrieve!));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasSession() {
|
Future<bool> hasSession() {
|
||||||
return _exists(_SESSION_KEY);
|
return _exists(_sessionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> removeSession() {
|
Future<bool> removeSession() {
|
||||||
return _remove(_SESSION_KEY);
|
return _remove(_sessionKey);
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import '../enums/swipe_event.dart';
|
|
||||||
|
|
||||||
class SwipeService {
|
|
||||||
StreamController<SwipeEvent> swipeEventController = StreamController<SwipeEvent>.broadcast();
|
|
||||||
|
|
||||||
void addEvent(SwipeEvent event) {
|
|
||||||
if (swipeEventController.hasListener) {
|
|
||||||
swipeEventController.add(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,8 +27,7 @@ class UserService {
|
||||||
try {
|
try {
|
||||||
await _fileService.getHistory();
|
await _fileService.getHistory();
|
||||||
} on ServiceException catch (e) {
|
} on ServiceException catch (e) {
|
||||||
throw new ServiceException(
|
throw ServiceException(code: ErrorCode.invalidApiKey, message: e.message);
|
||||||
code: ErrorCode.INVALID_API_KEY, message: e.message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,14 @@ 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.format(DateTime.fromMillisecondsSinceEpoch(millis as int));
|
return dateFormat
|
||||||
|
.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,20 +5,56 @@ import '../../core/util/logger.dart';
|
||||||
import '../enums/viewstate.dart';
|
import '../enums/viewstate.dart';
|
||||||
|
|
||||||
class BaseModel extends ChangeNotifier {
|
class BaseModel extends ChangeNotifier {
|
||||||
static const String STATE_VIEW = 'viewState';
|
static const String stateViewKey = 'viewState';
|
||||||
static const String STATE_MESSAGE = 'viewMessage';
|
static const String stateMessageKey = 'viewMessage';
|
||||||
|
|
||||||
final Logger _logger = getLogger();
|
final Logger _logger = getLogger();
|
||||||
|
|
||||||
bool _isDisposed = false;
|
bool _isDisposed = false;
|
||||||
|
|
||||||
Map<String, Object?> _stateMap = {STATE_VIEW: ViewState.Idle, STATE_MESSAGE: null};
|
final Map<String, Object?> _stateMap = {
|
||||||
|
stateViewKey: ViewState.idle,
|
||||||
|
stateMessageKey: null
|
||||||
|
};
|
||||||
|
|
||||||
ViewState? get state => _stateMap[STATE_VIEW] as ViewState?;
|
ViewState? get state => _stateMap[stateViewKey] as ViewState?;
|
||||||
|
|
||||||
String? get stateMessage => _stateMap[STATE_MESSAGE] as String?;
|
String? get stateMessage => _stateMap[stateMessageKey] as String?;
|
||||||
|
|
||||||
void setStateValue(String key, Object? stateValue) {
|
bool getStateValueAsBoolean(String key) {
|
||||||
|
if (_stateMap.containsKey(key) && _stateMap[key] is bool) {
|
||||||
|
return _stateMap[key] as bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? getStateValueAsString(String key) {
|
||||||
|
if (_stateMap.containsKey(key) && _stateMap[key] is String) {
|
||||||
|
return _stateMap[key] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? getStateValueAsInt(String key) {
|
||||||
|
if (_stateMap.containsKey(key) && _stateMap[key] is int) {
|
||||||
|
return _stateMap[key] as int;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStateBoolValue(String key, bool stateValue) =>
|
||||||
|
_setStateValue(key, stateValue);
|
||||||
|
|
||||||
|
void setStateIntValue(String key, int? stateValue) =>
|
||||||
|
_setStateValue(key, stateValue);
|
||||||
|
|
||||||
|
void setStateStringValue(String key, String? stateValue) =>
|
||||||
|
_setStateValue(key, stateValue);
|
||||||
|
|
||||||
|
void _setStateValue(String key, Object? stateValue) {
|
||||||
if (_stateMap.containsKey(key)) {
|
if (_stateMap.containsKey(key)) {
|
||||||
_stateMap.update(key, (value) => stateValue);
|
_stateMap.update(key, (value) => stateValue);
|
||||||
} else {
|
} else {
|
||||||
|
@ -27,7 +63,8 @@ class BaseModel extends ChangeNotifier {
|
||||||
|
|
||||||
if (!_isDisposed) {
|
if (!_isDisposed) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_logger.d("Notified state value update '($key, ${stateValue.toString()})'");
|
_logger
|
||||||
|
.d("Notified state value update '($key, ${stateValue.toString()})'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,15 +78,16 @@ class BaseModel extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStateView(ViewState stateView) {
|
void setStateView(ViewState stateView) {
|
||||||
setStateValue(STATE_VIEW, stateView);
|
_setStateValue(stateViewKey, stateView);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStateMessage(String? stateMessage) {
|
void setStateMessage(String? stateMessage) {
|
||||||
setStateValue(STATE_MESSAGE, stateMessage);
|
_setStateValue(stateMessageKey, stateMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_logger.d("Calling dispose");
|
||||||
super.dispose();
|
super.dispose();
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ class HistoryModel extends BaseModel {
|
||||||
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,13 +44,13 @@ 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(
|
||||||
|
@ -68,8 +68,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,
|
||||||
|
@ -101,19 +101,19 @@ class HistoryModel extends BaseModel {
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
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', e);
|
_logger.e('An unknown error occurred', error: e);
|
||||||
throw e;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future deletePaste(String id) async {
|
Future deletePaste(String id) async {
|
||||||
|
@ -128,7 +128,7 @@ class HistoryModel extends BaseModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateView(ViewState.Busy);
|
setStateView(ViewState.busy);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _fileService.deletePaste(id);
|
await _fileService.deletePaste(id);
|
||||||
|
@ -149,19 +149,19 @@ class HistoryModel extends BaseModel {
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
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', e);
|
_logger.e('An unknown error occurred', error: e);
|
||||||
throw e;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = new TextEditingController();
|
TextEditingController _uriController = TextEditingController();
|
||||||
|
|
||||||
final TextEditingController _userNameController = new TextEditingController();
|
final TextEditingController _userNameController = TextEditingController();
|
||||||
final TextEditingController _passwordController = new TextEditingController();
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
|
|
||||||
final TextEditingController _apiKeyController = new TextEditingController();
|
final TextEditingController _apiKeyController = 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 = new TextEditingController(text: s);
|
_uriController = 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,14 +121,14 @@ class LoginModel extends BaseModel {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
'apikey',
|
'apikey',
|
||||||
'fbmobile-${new DateTime.now().millisecondsSinceEpoch}');
|
'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 new ServiceException(
|
throw ServiceException(
|
||||||
code: ErrorCode.INVALID_API_KEY,
|
code: ErrorCode.invalidApiKey,
|
||||||
message: translate('login.errors.invalid_api_key'));
|
message: translate('login.errors.invalid_api_key'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -153,25 +153,25 @@ class LoginModel extends BaseModel {
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.INVALID_API_KEY) {
|
} else if (e is ServiceException && e.code == ErrorCode.invalidApiKey) {
|
||||||
errorMessage = translate('login.errors.invalid_api_key');
|
errorMessage = translate('login.errors.invalid_api_key');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
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', e);
|
_logger.e('An unknown error occurred', error: e);
|
||||||
throw e;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMessage!.isNotEmpty) {
|
if (errorMessage!.isNotEmpty) {
|
||||||
_sessionService.logout();
|
_sessionService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.idle);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:logger/logger.dart';
|
||||||
import '../../core/services/session_service.dart';
|
import '../../core/services/session_service.dart';
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
import '../enums/error_code.dart';
|
import '../enums/error_code.dart';
|
||||||
import '../enums/viewstate.dart';
|
|
||||||
import '../error/rest_service_exception.dart';
|
import '../error/rest_service_exception.dart';
|
||||||
import '../error/service_exception.dart';
|
import '../error/service_exception.dart';
|
||||||
import '../models/rest/config.dart';
|
import '../models/rest/config.dart';
|
||||||
|
@ -18,12 +17,16 @@ import '../util/logger.dart';
|
||||||
import 'base_model.dart';
|
import 'base_model.dart';
|
||||||
|
|
||||||
class ProfileModel extends BaseModel {
|
class ProfileModel extends BaseModel {
|
||||||
|
static const _configurationButtonLoading = 'configurationButtonLoading';
|
||||||
|
|
||||||
final SessionService _sessionService = locator<SessionService>();
|
final SessionService _sessionService = locator<SessionService>();
|
||||||
final DialogService _dialogService = locator<DialogService>();
|
final DialogService _dialogService = locator<DialogService>();
|
||||||
final LinkService _linkService = locator<LinkService>();
|
final LinkService _linkService = locator<LinkService>();
|
||||||
final FileService _fileService = locator<FileService>();
|
final FileService _fileService = locator<FileService>();
|
||||||
final Logger _logger = getLogger();
|
final Logger _logger = getLogger();
|
||||||
|
|
||||||
|
bool get configLoading => getStateValueAsBoolean(_configurationButtonLoading);
|
||||||
|
|
||||||
String? errorMessage;
|
String? errorMessage;
|
||||||
|
|
||||||
Future logout() async {
|
Future logout() async {
|
||||||
|
@ -44,7 +47,7 @@ class ProfileModel extends BaseModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future showConfig(String url) async {
|
Future showConfig(String url) async {
|
||||||
setStateView(ViewState.Busy);
|
setStateBoolValue(_configurationButtonLoading, true);
|
||||||
Config? config;
|
Config? config;
|
||||||
try {
|
try {
|
||||||
config = await _fileService.getConfig(url);
|
config = await _fileService.getConfig(url);
|
||||||
|
@ -65,21 +68,21 @@ class ProfileModel extends BaseModel {
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
errorMessage = translate('api.socket_timeout');
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('app.unknown_error');
|
errorMessage = translate('app.unknown_error');
|
||||||
setStateView(ViewState.Idle);
|
setStateBoolValue(_configurationButtonLoading, false);
|
||||||
_sessionService.logout();
|
_sessionService.logout();
|
||||||
setStateView(ViewState.Idle);
|
setStateBoolValue(_configurationButtonLoading, false);
|
||||||
_logger.e('An unknown error occurred', e);
|
_logger.e('An unknown error occurred', error: e);
|
||||||
throw e;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateView(ViewState.Idle);
|
setStateBoolValue(_configurationButtonLoading, false);
|
||||||
|
|
||||||
if (config != null && errorMessage == null) {
|
if (config != null && errorMessage == null) {
|
||||||
await _dialogService.showDialog(
|
await _dialogService.showDialog(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
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';
|
||||||
|
@ -9,19 +10,21 @@ 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(Duration(milliseconds: 150));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
|
||||||
setStateMessage(translate('startup.start_services'));
|
setStateMessage(translate('startup.start_services'));
|
||||||
await _sessionService.start();
|
await _sessionService.start();
|
||||||
await Future.delayed(Duration(milliseconds: 150));
|
await _permissionService.start();
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
|
||||||
_navigationService.navigateAndReplaceTo(HomeView.routeName);
|
_navigationService.navigateAndReplaceTo(HomeView.routeName);
|
||||||
|
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.idle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,15 @@ 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';
|
||||||
import '../enums/refresh_event.dart';
|
import '../enums/refresh_event.dart';
|
||||||
import '../enums/swipe_event.dart';
|
|
||||||
import '../enums/viewstate.dart';
|
import '../enums/viewstate.dart';
|
||||||
import '../error/rest_service_exception.dart';
|
import '../error/rest_service_exception.dart';
|
||||||
import '../error/service_exception.dart';
|
import '../error/service_exception.dart';
|
||||||
|
@ -22,7 +22,6 @@ import '../models/rest/uploaded_response.dart';
|
||||||
import '../services/file_service.dart';
|
import '../services/file_service.dart';
|
||||||
import '../services/link_service.dart';
|
import '../services/link_service.dart';
|
||||||
import '../services/refresh_service.dart';
|
import '../services/refresh_service.dart';
|
||||||
import '../services/swipe_service.dart';
|
|
||||||
import '../util/logger.dart';
|
import '../util/logger.dart';
|
||||||
import '../util/paste_util.dart';
|
import '../util/paste_util.dart';
|
||||||
import 'base_model.dart';
|
import 'base_model.dart';
|
||||||
|
@ -32,9 +31,8 @@ class UploadModel extends BaseModel {
|
||||||
final FileService _fileService = locator<FileService>();
|
final FileService _fileService = locator<FileService>();
|
||||||
final LinkService _linkService = locator<LinkService>();
|
final LinkService _linkService = locator<LinkService>();
|
||||||
final RefreshService _refreshService = locator<RefreshService>();
|
final RefreshService _refreshService = locator<RefreshService>();
|
||||||
final SwipeService _swipeService = locator<SwipeService>();
|
|
||||||
|
|
||||||
TextEditingController _pasteTextController = TextEditingController();
|
final TextEditingController _pasteTextController = TextEditingController();
|
||||||
bool pasteTextTouched = false;
|
bool pasteTextTouched = false;
|
||||||
|
|
||||||
late StreamSubscription _intentDataStreamSubscription;
|
late StreamSubscription _intentDataStreamSubscription;
|
||||||
|
@ -48,94 +46,75 @@ 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;
|
||||||
setStateValue("PASTE_TEXT_TOUCHED", pasteTextTouched);
|
setStateBoolValue("PASTE_TEXT_TOUCHED", pasteTextTouched);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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 = ReceiveSharingIntent.getMediaStream()
|
_intentDataStreamSubscription = FlutterSharingIntent.instance
|
||||||
.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);
|
|
||||||
if (paths!.isNotEmpty && paths!.length > 0) {
|
|
||||||
_swipeService.addEvent(SwipeEvent.Start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 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
|
||||||
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
|
FlutterSharingIntent.instance
|
||||||
if (value.length > 0) {
|
.getInitialSharing()
|
||||||
setStateView(ViewState.Busy);
|
.then((List<SharedFile> value) {
|
||||||
paths = value.map((sharedFile) {
|
_logger.d("Retrieved ${value.length} files from inactive intent");
|
||||||
return PlatformFile.fromMap({
|
_parseIntentFiles(value);
|
||||||
'path': sharedFile.path,
|
|
||||||
'name': basename(sharedFile.path),
|
|
||||||
'size': File(sharedFile.path).lengthSync(),
|
|
||||||
'bytes': null
|
|
||||||
});
|
|
||||||
}).toList();
|
|
||||||
setStateView(ViewState.Idle);
|
|
||||||
if (paths!.isNotEmpty && paths!.length > 0) {
|
|
||||||
_swipeService.addEvent(SwipeEvent.Start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 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);
|
|
||||||
_swipeService.addEvent(SwipeEvent.Start);
|
|
||||||
}
|
|
||||||
}, 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);
|
|
||||||
if (paths!.isNotEmpty && paths!.length > 0) {
|
|
||||||
_swipeService.addEvent(SwipeEvent.Start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 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);
|
||||||
|
|
||||||
_swipeService.addEvent(SwipeEvent.Start);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String? generatePasteLinks(Map<String, bool>? uploads, String url) {
|
String? generatePasteLinks(Map<String, bool>? uploads, String url) {
|
||||||
if (uploads != null && uploads.length > 0) {
|
if (uploads != null && uploads.isNotEmpty) {
|
||||||
var links = '';
|
var links = '';
|
||||||
|
|
||||||
uploads.forEach((id, isMulti) {
|
uploads.forEach((id, isMulti) {
|
||||||
|
@ -151,13 +130,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;
|
||||||
|
|
||||||
|
@ -173,52 +152,52 @@ class UploadModel extends BaseModel {
|
||||||
))
|
))
|
||||||
?.files;
|
?.files;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
_logger.e('Unsupported operation', e);
|
_logger.e('Unsupported operation', error: e);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
_logger.e('An unknown error occurred', ex);
|
_logger.e('An unknown error occurred', error: 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 = new Map();
|
Map<String, bool> uploadedPasteIds = {};
|
||||||
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-${(new DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt':
|
'paste-${(DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt':
|
||||||
pasteTextController.text
|
pasteTextController.text
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paths != null && paths!.length > 0) {
|
if (paths != null && paths!.isNotEmpty) {
|
||||||
files = paths!.map((e) => new File(e.path!)).toList();
|
files = paths!.map((e) => 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 =
|
||||||
|
@ -228,7 +207,7 @@ class UploadModel extends BaseModel {
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -251,21 +230,21 @@ class UploadModel extends BaseModel {
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_ERROR) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
||||||
errorMessage = translate('api.socket_error');
|
errorMessage = translate('api.socket_error');
|
||||||
} else if (e is ServiceException && e.code == ErrorCode.SOCKET_TIMEOUT) {
|
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
||||||
errorMessage = translate('api.socket_timeout');
|
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', e);
|
_logger.e('An unknown error occurred', error: e);
|
||||||
throw e;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateMessage(null);
|
setStateMessage(null);
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.idle);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +256,8 @@ 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,7 +11,6 @@ import 'core/services/permission_service.dart';
|
||||||
import 'core/services/refresh_service.dart';
|
import 'core/services/refresh_service.dart';
|
||||||
import 'core/services/session_service.dart';
|
import 'core/services/session_service.dart';
|
||||||
import 'core/services/storage_service.dart';
|
import 'core/services/storage_service.dart';
|
||||||
import 'core/services/swipe_service.dart';
|
|
||||||
import 'core/services/user_service.dart';
|
import 'core/services/user_service.dart';
|
||||||
import 'core/viewmodels/about_model.dart';
|
import 'core/viewmodels/about_model.dart';
|
||||||
import 'core/viewmodels/history_model.dart';
|
import 'core/viewmodels/history_model.dart';
|
||||||
|
@ -42,7 +41,6 @@ void setupLocator() {
|
||||||
locator.registerLazySingleton(() => LinkService());
|
locator.registerLazySingleton(() => LinkService());
|
||||||
locator.registerLazySingleton(() => PermissionService());
|
locator.registerLazySingleton(() => PermissionService());
|
||||||
locator.registerLazySingleton(() => RefreshService());
|
locator.registerLazySingleton(() => RefreshService());
|
||||||
locator.registerLazySingleton(() => SwipeService());
|
|
||||||
|
|
||||||
/// view models
|
/// view models
|
||||||
locator.registerFactory(() => StartUpViewModel());
|
locator.registerFactory(() => StartUpViewModel());
|
||||||
|
|
|
@ -9,10 +9,11 @@ import 'locator.dart';
|
||||||
/// main entry point used to configure log level, locales, ...
|
/// main entry point used to configure log level, locales, ...
|
||||||
void main() async {
|
void main() async {
|
||||||
setupLogger(Level.info);
|
setupLogger(Level.info);
|
||||||
// setupLogger(Level.debug);
|
|
||||||
setupLocator();
|
setupLocator();
|
||||||
|
|
||||||
var delegate = await LocalizationDelegate.create(fallbackLocale: 'en', supportedLocales: ['en']);
|
var delegate = await LocalizationDelegate.create(
|
||||||
|
fallbackLocale: 'en', supportedLocales: ['en', 'en_US']);
|
||||||
|
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
runApp(LocalizedApp(delegate, MyApp()));
|
runApp(LocalizedApp(delegate, MyApp()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,15 @@ 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: (_) => StartUpView());
|
return MaterialPageRoute(builder: (_) => const StartUpView());
|
||||||
case AboutView.routeName:
|
case AboutView.routeName:
|
||||||
return MaterialPageRoute(builder: (_) => AboutView());
|
return MaterialPageRoute(builder: (_) => const AboutView());
|
||||||
case HomeView.routeName:
|
case HomeView.routeName:
|
||||||
return MaterialPageRoute(builder: (_) => TabBarContainerView());
|
return MaterialPageRoute(builder: (_) => const TabBarContainerView());
|
||||||
case LoginView.routeName:
|
case LoginView.routeName:
|
||||||
return MaterialPageRoute(builder: (_) => LoginView());
|
return MaterialPageRoute(builder: (_) => LoginView());
|
||||||
case ProfileView.routeName:
|
case ProfileView.routeName:
|
||||||
return MaterialPageRoute(builder: (_) => ProfileView());
|
return MaterialPageRoute(builder: (_) => const ProfileView());
|
||||||
default:
|
default:
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (_) => Scaffold(
|
builder: (_) => Scaffold(
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
const Color backgroundColor = whiteColor;
|
|
||||||
|
|
||||||
/// Colors
|
|
||||||
const Color primaryBackgroundColor = whiteColor;
|
|
||||||
|
|
||||||
const Map<int, Color> colors = {
|
const Map<int, Color> colors = {
|
||||||
50: Color.fromRGBO(63, 69, 75, .1),
|
50: Color.fromRGBO(63, 69, 75, .1),
|
||||||
100: Color.fromRGBO(63, 69, 75, .2),
|
100: Color.fromRGBO(63, 69, 75, .2),
|
||||||
|
@ -19,14 +14,9 @@ const Map<int, Color> colors = {
|
||||||
};
|
};
|
||||||
const MaterialColor myColor = MaterialColor(0xFF3F454B, colors);
|
const MaterialColor myColor = MaterialColor(0xFF3F454B, colors);
|
||||||
const Color primaryAccentColor = myColor;
|
const Color primaryAccentColor = myColor;
|
||||||
const Color buttonBackgroundColor = primaryAccentColor;
|
|
||||||
const Color buttonForegroundColor = whiteColor;
|
|
||||||
|
|
||||||
const Color blueColor = Colors.blue;
|
const Color blueColor = Colors.blue;
|
||||||
const Color whiteColor = Colors.white;
|
const Color whiteColor = Colors.white;
|
||||||
const Color redColor = Colors.red;
|
const Color redColor = Colors.red;
|
||||||
const Color orangeColor = Colors.orange;
|
const Color orangeColor = Colors.orange;
|
||||||
const Color greenColor = Colors.green;
|
const Color greenColor = Colors.green;
|
||||||
|
|
||||||
/// Theme
|
|
||||||
const Brightness appBarBrightness = Brightness.dark;
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
const headerStyle = TextStyle(fontSize: 35, fontWeight: FontWeight.w900);
|
|
||||||
const subHeaderStyle = TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500);
|
const subHeaderStyle = TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -6,13 +6,14 @@ import '../../core/enums/viewstate.dart';
|
||||||
import '../../core/viewmodels/about_model.dart';
|
import '../../core/viewmodels/about_model.dart';
|
||||||
import '../../ui/shared/text_styles.dart';
|
import '../../ui/shared/text_styles.dart';
|
||||||
import '../../ui/shared/ui_helpers.dart';
|
import '../../ui/shared/ui_helpers.dart';
|
||||||
import '../shared/app_colors.dart';
|
|
||||||
import '../widgets/my_appbar.dart';
|
import '../widgets/my_appbar.dart';
|
||||||
import 'base_view.dart';
|
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(
|
||||||
|
@ -31,14 +32,14 @@ class AboutView extends StatelessWidget {
|
||||||
title: Text(translate('titles.about')),
|
title: Text(translate('titles.about')),
|
||||||
enableAbout: false,
|
enableAbout: false,
|
||||||
),
|
),
|
||||||
backgroundColor: backgroundColor,
|
body: model.state == ViewState.busy
|
||||||
body: model.state == ViewState.Busy
|
? const Center(child: CircularProgressIndicator())
|
||||||
? Center(child: CircularProgressIndicator())
|
|
||||||
: Container(
|
: Container(
|
||||||
padding: EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10, top: 10),
|
padding: const EdgeInsets.only(
|
||||||
|
left: 10.0, right: 10.0, bottom: 10, top: 10),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
Center(child: logo),
|
Center(child: logo),
|
||||||
|
@ -77,7 +78,7 @@ class AboutView extends StatelessWidget {
|
||||||
Center(
|
Center(
|
||||||
child: Linkify(
|
child: Linkify(
|
||||||
text: translate('about.website'),
|
text: translate('about.website'),
|
||||||
options: LinkifyOptions(humanize: false),
|
options: const 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;
|
||||||
|
|
||||||
BaseView({this.builder, this.onModelReady});
|
const BaseView({super.key, this.builder, this.onModelReady});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_BaseViewState<T> createState() => _BaseViewState<T>();
|
_BaseViewState<T> createState() => _BaseViewState<T>();
|
||||||
|
|
|
@ -13,12 +13,13 @@ import '../../core/viewmodels/history_model.dart';
|
||||||
import '../../ui/widgets/centered_error_row.dart';
|
import '../../ui/widgets/centered_error_row.dart';
|
||||||
import '../shared/app_colors.dart';
|
import '../shared/app_colors.dart';
|
||||||
import '../widgets/my_appbar.dart';
|
import '../widgets/my_appbar.dart';
|
||||||
import '../widgets/swipe_navigation.dart';
|
|
||||||
import 'base_view.dart';
|
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>(
|
||||||
|
@ -28,24 +29,23 @@ class HistoryView extends StatelessWidget {
|
||||||
},
|
},
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
appBar: MyAppBar(title: Text(translate('titles.history'))),
|
appBar: MyAppBar(title: Text(translate('titles.history'))),
|
||||||
backgroundColor: backgroundColor,
|
body: _render(model, context)),
|
||||||
body: SwipeNavigation(child: _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
|
||||||
? Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: (model.errorMessage == null
|
: (model.errorMessage == null
|
||||||
? Container(
|
? Container(
|
||||||
padding: EdgeInsets.all(0),
|
padding: const 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: EdgeInsets.all(25),
|
padding: const EdgeInsets.all(25),
|
||||||
child: CenteredErrorRow(
|
child: CenteredErrorRow(
|
||||||
model.errorMessage,
|
model.errorMessage,
|
||||||
retryCallback: () => model.getHistory(),
|
retryCallback: () => model.getHistory(),
|
||||||
|
@ -55,8 +55,8 @@ 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.length > 0) {
|
if (model.pastes.isNotEmpty) {
|
||||||
model.pastes.reversed.forEach((paste) {
|
for (var paste in model.pastes.reversed) {
|
||||||
List<Widget> widgets = [];
|
List<Widget> widgets = [];
|
||||||
|
|
||||||
var fullPasteUrl = PasteUtil.generateLink(url, paste.id);
|
var fullPasteUrl = PasteUtil.generateLink(url, paste.id);
|
||||||
|
@ -76,7 +76,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: Icon(Icons.copy,
|
icon: const 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) {
|
||||||
|
@ -89,16 +89,18 @@ class HistoryView extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
content: Text(translate('history.copy_link.copied')),
|
content: Text(translate('history.copy_link.copied')),
|
||||||
duration: Duration(seconds: 10),
|
duration: const Duration(seconds: 10),
|
||||||
);
|
);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
if (context.mounted) {
|
||||||
|
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: Icon(Icons.delete, color: redColor),
|
icon: const Icon(Icons.delete, color: redColor),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.deletePaste(paste.id);
|
model.deletePaste(paste.id);
|
||||||
}));
|
}));
|
||||||
|
@ -126,13 +128,13 @@ class HistoryView extends StatelessWidget {
|
||||||
widgets.add(fileSizeWidget);
|
widgets.add(fileSizeWidget);
|
||||||
widgets.add(mimeTypeWidget);
|
widgets.add(mimeTypeWidget);
|
||||||
} else {
|
} else {
|
||||||
paste.items!.forEach((element) {
|
for (var element in paste.items!) {
|
||||||
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);
|
||||||
|
@ -141,7 +143,7 @@ class HistoryView extends StatelessWidget {
|
||||||
widgets.add(deleteWidget);
|
widgets.add(deleteWidget);
|
||||||
|
|
||||||
var expandable = ExpandableTheme(
|
var expandable = ExpandableTheme(
|
||||||
data: ExpandableThemeData(
|
data: const ExpandableThemeData(
|
||||||
iconPlacement: ExpandablePanelIconPlacement.right,
|
iconPlacement: ExpandablePanelIconPlacement.right,
|
||||||
headerAlignment: ExpandablePanelHeaderAlignment.center,
|
headerAlignment: ExpandablePanelHeaderAlignment.center,
|
||||||
hasIcon: true,
|
hasIcon: true,
|
||||||
|
@ -152,7 +154,7 @@ class HistoryView extends StatelessWidget {
|
||||||
header: InkWell(
|
header: InkWell(
|
||||||
child: Text(
|
child: Text(
|
||||||
paste.id,
|
paste.id,
|
||||||
style: TextStyle(color: blueColor),
|
style: const TextStyle(color: blueColor),
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
)),
|
)),
|
||||||
expanded: Column(
|
expanded: Column(
|
||||||
|
@ -168,17 +170,17 @@ class HistoryView extends StatelessWidget {
|
||||||
trailing: Wrap(children: [
|
trailing: Wrap(children: [
|
||||||
openInBrowserButton,
|
openInBrowserButton,
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.share,
|
icon: const 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(
|
||||||
|
@ -189,14 +191,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: Icon(Icons.open_in_new,
|
icon: const 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);
|
||||||
|
|
|
@ -3,20 +3,22 @@ import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
|
||||||
import '../../core/enums/viewstate.dart';
|
import '../../core/enums/viewstate.dart';
|
||||||
import '../../core/viewmodels/home_model.dart';
|
import '../../core/viewmodels/home_model.dart';
|
||||||
import '../shared/app_colors.dart';
|
|
||||||
import '../widgets/my_appbar.dart';
|
import '../widgets/my_appbar.dart';
|
||||||
import 'base_view.dart';
|
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'))),
|
||||||
backgroundColor: backgroundColor,
|
body: model.state == ViewState.busy
|
||||||
body: model.state == ViewState.Busy ? Center(child: CircularProgressIndicator()) : Container()),
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Container()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ 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';
|
||||||
|
@ -21,6 +20,8 @@ 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(
|
||||||
|
@ -36,12 +37,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'))),
|
||||||
backgroundColor: backgroundColor,
|
body: model.state == ViewState.busy
|
||||||
body: model.state == ViewState.Busy
|
? const Center(child: CircularProgressIndicator())
|
||||||
? Center(child: CircularProgressIndicator())
|
|
||||||
: ListView(
|
: ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
padding: EdgeInsets.only(left: 10.0, right: 10.0),
|
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
Center(child: logo),
|
Center(child: logo),
|
||||||
|
@ -51,12 +51,8 @@ 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: Icon(Icons.help, color: buttonBackgroundColor),
|
child: const Icon(Icons.help),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_dialogService.showDialog(
|
_dialogService.showDialog(
|
||||||
title: translate(
|
title: translate(
|
||||||
|
@ -89,9 +85,9 @@ class LoginView extends StatelessWidget {
|
||||||
uriController: model.uriController,
|
uriController: model.uriController,
|
||||||
apiKeyController: model.apiKeyController),
|
apiKeyController: model.apiKeyController),
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
ElevatedButton(
|
ElevatedButton.icon(
|
||||||
child: Text(translate('login.button'),
|
icon: const Icon(Icons.login, color: blueColor),
|
||||||
style: TextStyle(color: buttonForegroundColor)),
|
label: Text(translate('login.button')),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var loginSuccess = await model.login();
|
var loginSuccess = await model.login();
|
||||||
if (loginSuccess) {
|
if (loginSuccess) {
|
||||||
|
|
73
lib/ui/views/navbar_authenticated.dart
Normal file
73
lib/ui/views/navbar_authenticated.dart
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import 'package:fbmobile/core/util/logger.dart';
|
||||||
|
import 'package:fbmobile/ui/views/profile_view.dart';
|
||||||
|
import 'package:fbmobile/ui/views/upload_view.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
|
import '../shared/app_colors.dart';
|
||||||
|
import 'history_view.dart';
|
||||||
|
|
||||||
|
class AuthenticatedNavBarView extends StatefulWidget {
|
||||||
|
const AuthenticatedNavBarView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
AuthenticatedNavBarState createState() => AuthenticatedNavBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthenticatedNavBarState extends State<AuthenticatedNavBarView>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
final Logger _logger = getLogger();
|
||||||
|
int _currentTabIndex = 0;
|
||||||
|
|
||||||
|
void updateIndex(int targetIndex) {
|
||||||
|
setState(() {
|
||||||
|
_currentTabIndex = targetIndex;
|
||||||
|
_logger.d("Changing current tab index to '$targetIndex'");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
bottomNavigationBar: NavigationBar(
|
||||||
|
key: UniqueKey(),
|
||||||
|
onDestinationSelected: (int index) {
|
||||||
|
updateIndex(index);
|
||||||
|
},
|
||||||
|
selectedIndex: _currentTabIndex,
|
||||||
|
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
|
||||||
|
destinations: const <Widget>[
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.upload_outlined),
|
||||||
|
label: 'Upload',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.history_outlined),
|
||||||
|
label: 'History',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.person_outlined),
|
||||||
|
label: 'Profile',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: <Widget>[
|
||||||
|
Container(
|
||||||
|
color: myColor,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: const UploadView(),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: myColor,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: const HistoryView(),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: myColor,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: const ProfileView(),
|
||||||
|
),
|
||||||
|
][_currentTabIndex],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,27 +10,27 @@ import '../shared/app_colors.dart';
|
||||||
import '../shared/text_styles.dart';
|
import '../shared/text_styles.dart';
|
||||||
import '../shared/ui_helpers.dart';
|
import '../shared/ui_helpers.dart';
|
||||||
import '../widgets/my_appbar.dart';
|
import '../widgets/my_appbar.dart';
|
||||||
import '../widgets/swipe_navigation.dart';
|
|
||||||
import 'base_view.dart';
|
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) => Scaffold(
|
||||||
appBar: MyAppBar(title: Text(translate('titles.profile'))),
|
appBar: MyAppBar(title: Text(translate('titles.profile'))),
|
||||||
backgroundColor: backgroundColor,
|
body: _render(model, context)));
|
||||||
body: SwipeNavigation(child: _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
|
||||||
? Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: ListView(
|
: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
|
@ -48,16 +48,27 @@ 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: LinkifyOptions(humanize: false),
|
options: const 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: ElevatedButton.icon(
|
||||||
icon: Icon(Icons.settings, color: blueColor),
|
icon: model.configLoading
|
||||||
|
? Container(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
padding: const EdgeInsets.all(2.0),
|
||||||
|
child: const CircularProgressIndicator(
|
||||||
|
color: blueColor,
|
||||||
|
strokeWidth: 3,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.settings, color: blueColor),
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('profile.show_config'),
|
model.configLoading
|
||||||
style: TextStyle(color: buttonForegroundColor),
|
? translate('profile.show_config_loading')
|
||||||
|
: translate('profile.show_config'),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await model.showConfig(url);
|
await model.showConfig(url);
|
||||||
|
@ -66,10 +77,9 @@ 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: Icon(Icons.lock, color: orangeColor),
|
icon: const Icon(Icons.lock, color: orangeColor),
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('profile.reveal_api_key'),
|
translate('profile.reveal_api_key'),
|
||||||
style: TextStyle(color: buttonForegroundColor),
|
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.revealApiKey(apiKey);
|
model.revealApiKey(apiKey);
|
||||||
|
@ -78,10 +88,9 @@ 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: Icon(Icons.exit_to_app, color: redColor),
|
icon: const Icon(Icons.exit_to_app, color: redColor),
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('profile.logout'),
|
translate('profile.logout'),
|
||||||
style: TextStyle(color: buttonForegroundColor),
|
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await model.logout();
|
await model.logout();
|
||||||
|
|
|
@ -3,26 +3,28 @@ 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(),
|
||||||
onModelReady: (model) => model.handleStartUpLogic(),
|
onViewModelReady: (model) => model.handleStartUpLogic(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
backgroundColor: whiteColor,
|
body: model.state == ViewState.busy
|
||||||
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: [
|
||||||
CircularProgressIndicator(),
|
const CircularProgressIndicator(),
|
||||||
(model.stateMessage!.isNotEmpty ? Text(model.stateMessage!) : Container())
|
(model.stateMessage!.isNotEmpty
|
||||||
|
? Text(model.stateMessage!)
|
||||||
|
: Container())
|
||||||
]))
|
]))
|
||||||
: Container()));
|
: Container()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
|
||||||
|
|
||||||
import '../shared/app_colors.dart';
|
|
||||||
import 'login_view.dart';
|
|
||||||
|
|
||||||
class AnonymousTabBarView extends StatefulWidget {
|
|
||||||
@override
|
|
||||||
AnonymousTabBarState createState() => AnonymousTabBarState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class AnonymousTabBarState extends State<AnonymousTabBarView>
|
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
TabController? _tabController;
|
|
||||||
int _currentTabIndex = 0;
|
|
||||||
|
|
||||||
List<Widget> _realPages = [LoginView()];
|
|
||||||
List<Widget> _tabPages = [LoginView()];
|
|
||||||
List<bool> _hasInit = [true];
|
|
||||||
|
|
||||||
List<Widget> _tabsButton = [
|
|
||||||
Tab(
|
|
||||||
icon: Icon(Icons.person_outline, color: blueColor),
|
|
||||||
child: Text(
|
|
||||||
translate('tabs.login'),
|
|
||||||
style: TextStyle(color: blueColor),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_tabController = TabController(length: _realPages.length, vsync: this)
|
|
||||||
..addListener(() {
|
|
||||||
int selectedIndex = _tabController!.index;
|
|
||||||
if (_currentTabIndex != selectedIndex) {
|
|
||||||
if (!_hasInit[selectedIndex]) {
|
|
||||||
_tabPages[selectedIndex] = _realPages[selectedIndex];
|
|
||||||
_hasInit[selectedIndex] = true;
|
|
||||||
}
|
|
||||||
setState(() => _currentTabIndex = selectedIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_tabController!.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
body: IndexedStack(index: _currentTabIndex, children: _tabPages),
|
|
||||||
bottomNavigationBar: BottomAppBar(
|
|
||||||
child: TabBar(
|
|
||||||
labelColor: primaryAccentColor,
|
|
||||||
indicatorColor: blueColor,
|
|
||||||
indicatorWeight: 3.0,
|
|
||||||
tabs: _tabsButton,
|
|
||||||
controller: _tabController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
|
||||||
import 'package:logger/logger.dart';
|
|
||||||
|
|
||||||
import '../../core/enums/swipe_event.dart';
|
|
||||||
import '../../core/services/swipe_service.dart';
|
|
||||||
import '../../core/util/logger.dart';
|
|
||||||
import '../../locator.dart';
|
|
||||||
import '../shared/app_colors.dart';
|
|
||||||
import 'history_view.dart';
|
|
||||||
import 'profile_view.dart';
|
|
||||||
import 'upload_view.dart';
|
|
||||||
|
|
||||||
class AuthenticatedTabBarView extends StatefulWidget {
|
|
||||||
@override
|
|
||||||
AuthenticatedTabBarState createState() => AuthenticatedTabBarState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthenticatedTabBarState extends State<AuthenticatedTabBarView>
|
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
final Logger _logger = getLogger();
|
|
||||||
final SwipeService _swipeService = locator<SwipeService>();
|
|
||||||
|
|
||||||
late StreamSubscription _swipeEventSubscription;
|
|
||||||
TabController? _tabController;
|
|
||||||
int _currentTabIndex = 0;
|
|
||||||
|
|
||||||
List<Widget> _realPages = [UploadView(), HistoryView(), ProfileView()];
|
|
||||||
List<Widget> _tabPages = [
|
|
||||||
UploadView(),
|
|
||||||
Container(),
|
|
||||||
Container(),
|
|
||||||
];
|
|
||||||
List<bool> _hasInit = [true, false, false];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_tabController = TabController(length: _realPages.length, vsync: this)
|
|
||||||
..addListener(() {
|
|
||||||
int selectedIndex = _tabController!.index;
|
|
||||||
if (_currentTabIndex != selectedIndex) {
|
|
||||||
if (!_hasInit[selectedIndex]) {
|
|
||||||
_tabPages[selectedIndex] = _realPages[selectedIndex];
|
|
||||||
_hasInit[selectedIndex] = true;
|
|
||||||
}
|
|
||||||
setState(() => _currentTabIndex = selectedIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_swipeEventSubscription =
|
|
||||||
_swipeService.swipeEventController.stream.listen((SwipeEvent event) {
|
|
||||||
_logger.d('Received a swipe event for the authenticated tab bar: $event');
|
|
||||||
|
|
||||||
int targetIndex = _currentTabIndex;
|
|
||||||
if (SwipeEvent.Left == event) {
|
|
||||||
targetIndex = min(_currentTabIndex + 1, _realPages.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SwipeEvent.Right == event) {
|
|
||||||
targetIndex = max(_currentTabIndex - 1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SwipeEvent.Start == event) {
|
|
||||||
targetIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SwipeEvent.End == event) {
|
|
||||||
targetIndex = _tabPages.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.d(
|
|
||||||
"Changing to tab '$targetIndex' because of a swipe event '$event'");
|
|
||||||
_tabController!.animateTo(targetIndex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_tabController!.dispose();
|
|
||||||
_swipeEventSubscription.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
double width = MediaQuery.of(context).size.width;
|
|
||||||
double yourWidth = width / 3;
|
|
||||||
double yourHeight = 55;
|
|
||||||
|
|
||||||
Color colorTabItem0 =
|
|
||||||
_currentTabIndex == 0 ? blueColor : primaryAccentColor;
|
|
||||||
Color colorTabItem1 =
|
|
||||||
_currentTabIndex == 1 ? blueColor : primaryAccentColor;
|
|
||||||
Color colorTabItem2 =
|
|
||||||
_currentTabIndex == 2 ? blueColor : primaryAccentColor;
|
|
||||||
|
|
||||||
List<Widget> _tabsButton = [
|
|
||||||
Container(
|
|
||||||
width: yourWidth,
|
|
||||||
height: yourHeight,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Tab(
|
|
||||||
icon: Icon(
|
|
||||||
_currentTabIndex == 0
|
|
||||||
? Icons.upload_outlined
|
|
||||||
: Icons.upload_rounded,
|
|
||||||
color: colorTabItem0,
|
|
||||||
),
|
|
||||||
child: Text(translate('tabs.upload'),
|
|
||||||
style: TextStyle(color: colorTabItem0)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: yourWidth,
|
|
||||||
height: yourHeight,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Tab(
|
|
||||||
icon: Icon(
|
|
||||||
_currentTabIndex == 1
|
|
||||||
? Icons.history_outlined
|
|
||||||
: Icons.history_rounded,
|
|
||||||
color: colorTabItem1,
|
|
||||||
),
|
|
||||||
child: Text(translate('tabs.history'),
|
|
||||||
style: TextStyle(color: colorTabItem1)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: yourWidth,
|
|
||||||
height: yourHeight,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Tab(
|
|
||||||
icon: Icon(
|
|
||||||
_currentTabIndex == 2
|
|
||||||
? Icons.person_outlined
|
|
||||||
: Icons.person_rounded,
|
|
||||||
color: colorTabItem2,
|
|
||||||
),
|
|
||||||
child: Text(translate('tabs.profile'),
|
|
||||||
style: TextStyle(color: colorTabItem2)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: IndexedStack(index: _currentTabIndex, children: _tabPages),
|
|
||||||
bottomNavigationBar: BottomAppBar(
|
|
||||||
child: TabBar(
|
|
||||||
indicatorSize: TabBarIndicatorSize.label,
|
|
||||||
labelColor: primaryAccentColor,
|
|
||||||
indicatorColor: blueColor,
|
|
||||||
indicatorWeight: 3.0,
|
|
||||||
labelPadding: EdgeInsets.all(0),
|
|
||||||
tabs: _tabsButton,
|
|
||||||
isScrollable: true,
|
|
||||||
controller: _tabController,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
import 'package:fbmobile/ui/views/login_view.dart';
|
||||||
|
import 'package:fbmobile/ui/views/navbar_authenticated.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../../core/models/session.dart';
|
import '../../core/models/session.dart';
|
||||||
import 'tabbar_anonymous.dart';
|
|
||||||
import 'tabbar_authenticated.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);
|
||||||
|
@ -13,9 +15,9 @@ class TabBarContainerView extends StatelessWidget {
|
||||||
currentSession != null ? currentSession.apiKey.isNotEmpty : false;
|
currentSession != null ? currentSession.apiKey.isNotEmpty : false;
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return AuthenticatedTabBarView();
|
return const AuthenticatedNavBarView();
|
||||||
}
|
}
|
||||||
|
|
||||||
return AnonymousTabBarView();
|
return LoginView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
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';
|
||||||
|
@ -9,37 +10,37 @@ import '../../core/viewmodels/upload_model.dart';
|
||||||
import '../shared/app_colors.dart';
|
import '../shared/app_colors.dart';
|
||||||
import '../widgets/centered_error_row.dart';
|
import '../widgets/centered_error_row.dart';
|
||||||
import '../widgets/my_appbar.dart';
|
import '../widgets/my_appbar.dart';
|
||||||
import '../widgets/swipe_navigation.dart';
|
|
||||||
import 'base_view.dart';
|
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) => Scaffold(
|
||||||
appBar: MyAppBar(title: Text(translate('titles.upload'))),
|
appBar: MyAppBar(title: Text(translate('titles.upload'))),
|
||||||
backgroundColor: backgroundColor,
|
body: _render(model, context)));
|
||||||
body: SwipeNavigation(child: _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: [
|
||||||
CircularProgressIndicator(),
|
const CircularProgressIndicator(),
|
||||||
(model.stateMessage != null && model.stateMessage!.isNotEmpty
|
(model.stateMessage != null && model.stateMessage!.isNotEmpty
|
||||||
? Text(model.stateMessage!)
|
? Text(model.stateMessage!)
|
||||||
: Container())
|
: Container())
|
||||||
|
@ -56,18 +57,17 @@ class UploadView extends StatelessWidget {
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: 7,
|
maxLines: 7,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: Icon(
|
prefixIcon: const Icon(
|
||||||
Icons.text_snippet,
|
Icons.text_snippet,
|
||||||
color: buttonBackgroundColor,
|
|
||||||
),
|
),
|
||||||
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:
|
contentPadding: const EdgeInsets.fromLTRB(
|
||||||
EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
20.0, 10.0, 20.0, 10.0),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(32.0)),
|
borderRadius: BorderRadius.circular(32.0)),
|
||||||
),
|
),
|
||||||
|
@ -85,24 +85,21 @@ class UploadView extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: Icon(Icons.file_copy_sharp,
|
icon: const 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'),
|
||||||
style:
|
|
||||||
TextStyle(color: buttonForegroundColor),
|
|
||||||
)),
|
)),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: Icon(Icons.cancel, color: orangeColor),
|
icon: const Icon(Icons.cancel,
|
||||||
|
color: orangeColor),
|
||||||
onPressed: model.paths != null &&
|
onPressed: model.paths != null &&
|
||||||
model.paths!.length > 0
|
model.paths!.isNotEmpty
|
||||||
? () => model.clearCachedFiles()
|
? () => model.clearCachedFiles()
|
||||||
: null,
|
: null,
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('upload.clear_temporary_files'),
|
translate('upload.clear_temporary_files'),
|
||||||
style:
|
|
||||||
TextStyle(color: buttonForegroundColor),
|
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
@ -151,19 +148,20 @@ class UploadView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
content: Text(translate(
|
content: Text(translate(
|
||||||
'upload.uploaded')),
|
'upload.uploaded')),
|
||||||
duration: Duration(seconds: 10),
|
duration:
|
||||||
|
const Duration(seconds: 10),
|
||||||
);
|
);
|
||||||
ScaffoldMessenger.of(context)
|
if (context.mounted) {
|
||||||
.showSnackBar(snackBar);
|
ScaffoldMessenger.of(context)
|
||||||
|
.showSnackBar(snackBar);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.upload_rounded,
|
icon: const Icon(Icons.upload_rounded,
|
||||||
color: greenColor),
|
color: greenColor),
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('upload.upload'),
|
translate('upload.upload'),
|
||||||
style:
|
|
||||||
TextStyle(color: buttonForegroundColor),
|
|
||||||
)),
|
)),
|
||||||
])),
|
])),
|
||||||
model.errorMessage != null && model.errorMessage!.isNotEmpty
|
model.errorMessage != null && model.errorMessage!.isNotEmpty
|
||||||
|
@ -174,13 +172,13 @@ class UploadView extends StatelessWidget {
|
||||||
: Container(),
|
: Container(),
|
||||||
Builder(
|
Builder(
|
||||||
builder: (BuildContext context) => model.loadingPath
|
builder: (BuildContext context) => model.loadingPath
|
||||||
? Padding(
|
? const Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 10.0),
|
padding: EdgeInsets.only(bottom: 10.0),
|
||||||
child: const CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
)
|
)
|
||||||
: model.paths != null
|
: model.paths != null
|
||||||
? Container(
|
? Container(
|
||||||
padding: const EdgeInsets.only(bottom: 30.0),
|
padding: const EdgeInsets.only(bottom: 20.0),
|
||||||
height:
|
height:
|
||||||
MediaQuery.of(context).size.height * 0.50,
|
MediaQuery.of(context).size.height * 0.50,
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
|
@ -198,7 +196,13 @@ class UploadView extends StatelessWidget {
|
||||||
.map((e) => e.name)
|
.map((e) => e.name)
|
||||||
.toList()[index]
|
.toList()[index]
|
||||||
: model.fileName ?? '...');
|
: model.fileName ?? '...');
|
||||||
final path = model.paths!.length > 0
|
final size = model.paths!.isNotEmpty
|
||||||
|
? model.paths!
|
||||||
|
.map((e) => e.size)
|
||||||
|
.toList()[index]
|
||||||
|
.toString()
|
||||||
|
: '';
|
||||||
|
final path = model.paths!.isNotEmpty
|
||||||
? model.paths!
|
? model.paths!
|
||||||
.map((e) => e.path)
|
.map((e) => e.path)
|
||||||
.toList()[index]
|
.toList()[index]
|
||||||
|
@ -207,8 +211,14 @@ class UploadView extends StatelessWidget {
|
||||||
|
|
||||||
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,
|
"$name (${FormatterUtil.formatBytes(int.parse(size), 2)})",
|
||||||
),
|
),
|
||||||
subtitle: Text(path),
|
subtitle: Text(path),
|
||||||
));
|
));
|
||||||
|
|
|
@ -3,18 +3,16 @@ 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();
|
AboutIconButton({super.key});
|
||||||
|
|
||||||
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: Icon(Icons.help),
|
icon: const 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;
|
||||||
|
|
||||||
CenteredErrorRow(this.message, {this.retryCallback});
|
const CenteredErrorRow(this.message, {super.key, this.retryCallback});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -20,7 +20,10 @@ class CenteredErrorRow extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(child: Center(child: Text(message!, style: TextStyle(color: redColor)))),
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(message!,
|
||||||
|
style: const TextStyle(color: redColor)))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(retryCallback != null
|
(retryCallback != null
|
||||||
|
@ -30,7 +33,7 @@ class CenteredErrorRow extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Center(
|
Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh),
|
||||||
color: primaryAccentColor,
|
color: primaryAccentColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
retryCallback!();
|
retryCallback!();
|
||||||
|
|
|
@ -10,18 +10,25 @@ class LoginApiKeyHeaders extends StatelessWidget {
|
||||||
|
|
||||||
final String? validationMessage;
|
final String? validationMessage;
|
||||||
|
|
||||||
LoginApiKeyHeaders({required this.uriController, required this.apiKeyController, this.validationMessage});
|
const LoginApiKeyHeaders(
|
||||||
|
{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>[
|
||||||
this.validationMessage != null ? Text(validationMessage!, style: TextStyle(color: redColor)) : Container(),
|
validationMessage != null
|
||||||
LoginTextField(uriController, translate('login.url_placeholder'), Icon(Icons.link),
|
? Text(validationMessage!, style: const TextStyle(color: redColor))
|
||||||
|
: 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'),
|
||||||
Icon(Icons.vpn_key),
|
const Icon(Icons.vpn_key),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -11,8 +11,9 @@ class LoginCredentialsHeaders extends StatelessWidget {
|
||||||
|
|
||||||
final String? validationMessage;
|
final String? validationMessage;
|
||||||
|
|
||||||
LoginCredentialsHeaders(
|
const LoginCredentialsHeaders(
|
||||||
{required this.uriController,
|
{super.key,
|
||||||
|
required this.uriController,
|
||||||
required this.usernameController,
|
required this.usernameController,
|
||||||
required this.passwordController,
|
required this.passwordController,
|
||||||
this.validationMessage});
|
this.validationMessage});
|
||||||
|
@ -20,12 +21,17 @@ class LoginCredentialsHeaders extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(children: <Widget>[
|
return Column(children: <Widget>[
|
||||||
this.validationMessage != null ? Text(validationMessage!, style: TextStyle(color: redColor)) : Container(),
|
validationMessage != null
|
||||||
LoginTextField(uriController, translate('login.url_placeholder'), Icon(Icons.link),
|
? Text(validationMessage!, style: const TextStyle(color: redColor))
|
||||||
|
: Container(),
|
||||||
|
LoginTextField(uriController, translate('login.url_placeholder'),
|
||||||
|
const Icon(Icons.link),
|
||||||
keyboardType: TextInputType.url),
|
keyboardType: TextInputType.url),
|
||||||
LoginTextField(usernameController, translate('login.username_placeholder'), Icon(Icons.person),
|
LoginTextField(usernameController,
|
||||||
|
translate('login.username_placeholder'), const Icon(Icons.person),
|
||||||
keyboardType: TextInputType.name),
|
keyboardType: TextInputType.name),
|
||||||
LoginTextField(passwordController, translate('login.password_placeholder'), Icon(Icons.vpn_key),
|
LoginTextField(passwordController,
|
||||||
|
translate('login.password_placeholder'), const Icon(Icons.vpn_key),
|
||||||
obscureText: true),
|
obscureText: true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
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;
|
||||||
|
@ -9,29 +7,31 @@ class LoginTextField extends StatelessWidget {
|
||||||
final bool obscureText;
|
final bool obscureText;
|
||||||
final Widget prefixIcon;
|
final Widget prefixIcon;
|
||||||
|
|
||||||
LoginTextField(this.controller, this.placeHolder, this.prefixIcon,
|
const LoginTextField(this.controller, this.placeHolder, this.prefixIcon,
|
||||||
{this.keyboardType = TextInputType.text, this.obscureText = false});
|
{super.key,
|
||||||
|
this.keyboardType = TextInputType.text,
|
||||||
|
this.obscureText = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||||
margin: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
|
margin: const 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: Icon(Icons.clear),
|
icon: const Icon(Icons.clear),
|
||||||
),
|
),
|
||||||
prefixIcon: prefixIcon,
|
prefixIcon: prefixIcon,
|
||||||
hintText: placeHolder,
|
hintText: placeHolder,
|
||||||
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
contentPadding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
border:
|
||||||
|
OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
||||||
),
|
),
|
||||||
controller: controller),
|
controller: controller),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import '../shared/app_colors.dart';
|
|
||||||
import '../widgets/about_iconbutton.dart';
|
import '../widgets/about_iconbutton.dart';
|
||||||
|
|
||||||
class MyAppBar extends AppBar {
|
class MyAppBar extends AppBar {
|
||||||
|
@ -9,25 +7,17 @@ class MyAppBar extends AppBar {
|
||||||
static final List<Widget> aboutDisabledWidgets = [];
|
static final List<Widget> aboutDisabledWidgets = [];
|
||||||
|
|
||||||
MyAppBar(
|
MyAppBar(
|
||||||
{Key? key,
|
{super.key,
|
||||||
required Widget title,
|
required Widget title,
|
||||||
List<Widget>? actionWidgets,
|
List<Widget>? actionWidgets,
|
||||||
bool enableAbout = true})
|
bool enableAbout = true})
|
||||||
: super(
|
: super(
|
||||||
key: key,
|
|
||||||
title: Row(children: <Widget>[title]),
|
title: Row(children: <Widget>[title]),
|
||||||
actions: _renderIconButtons(actionWidgets, enableAbout),
|
actions: _renderIconButtons(actionWidgets, enableAbout));
|
||||||
systemOverlayStyle: SystemUiOverlayStyle(
|
|
||||||
systemNavigationBarColor: primaryAccentColor, // Navigation bar
|
|
||||||
statusBarColor: primaryAccentColor, // Status bar
|
|
||||||
),
|
|
||||||
backgroundColor: primaryAccentColor);
|
|
||||||
|
|
||||||
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];
|
||||||
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:simple_gesture_detector/simple_gesture_detector.dart';
|
|
||||||
|
|
||||||
import '../../core/enums/swipe_event.dart';
|
|
||||||
import '../../core/services/swipe_service.dart';
|
|
||||||
import '../../locator.dart';
|
|
||||||
|
|
||||||
class SwipeNavigation extends StatefulWidget {
|
|
||||||
/// Widget to be augmented with gesture detection.
|
|
||||||
final Widget? child;
|
|
||||||
|
|
||||||
/// Creates a [SwipeNavigation] widget.
|
|
||||||
const SwipeNavigation({
|
|
||||||
Key? key,
|
|
||||||
this.child,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_SwipeNavigationState createState() => _SwipeNavigationState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SwipeNavigationState extends State<SwipeNavigation> {
|
|
||||||
final SwipeService _swipeService = locator<SwipeService>();
|
|
||||||
|
|
||||||
void _onHorizontalSwipe(SwipeDirection direction) {
|
|
||||||
if (direction == SwipeDirection.left) {
|
|
||||||
_swipeService.addEvent(SwipeEvent.Left);
|
|
||||||
} else {
|
|
||||||
_swipeService.addEvent(SwipeEvent.Right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SimpleGestureDetector(
|
|
||||||
onHorizontalSwipe: _onHorizontalSwipe, child: widget.child!);
|
|
||||||
}
|
|
||||||
}
|
|
850
pubspec.lock
850
pubspec.lock
File diff suppressed because it is too large
Load diff
48
pubspec.yaml
48
pubspec.yaml
|
@ -11,43 +11,47 @@ 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.4.1+14
|
version: 1.6.4+22
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.15.1 <3.0.0'
|
sdk: '>=3.5.0 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cupertino_icons: 1.0.4
|
cupertino_icons: 1.0.8
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_translate: 3.0.1
|
flutter_translate: 4.1.0
|
||||||
provider: 6.0.2
|
provider: 6.1.2
|
||||||
stacked: 2.2.8
|
stacked: 3.4.3
|
||||||
get_it: 7.2.0
|
get_it: 7.7.0
|
||||||
logger: 1.1.0
|
logger: 2.4.0
|
||||||
shared_preferences: 2.0.12
|
shared_preferences: 2.3.2
|
||||||
http: 0.13.4
|
http: 1.2.2
|
||||||
validators: 3.0.0
|
validators: 3.0.0
|
||||||
flutter_linkify: 5.0.2
|
flutter_linkify: 6.0.0
|
||||||
url_launcher: 6.0.18
|
url_launcher: 6.3.1
|
||||||
expandable: 5.0.1
|
expandable: 5.0.1
|
||||||
share_plus: 3.0.4
|
share_plus: 10.1.1
|
||||||
file_picker: 4.3.2
|
file_picker: 8.1.3
|
||||||
clipboard: 0.1.3
|
clipboard: 0.1.3
|
||||||
receive_sharing_intent: 1.4.5
|
permission_handler: 11.3.1
|
||||||
permission_handler: 8.3.0
|
package_info_plus: 8.1.0
|
||||||
package_info_plus: 1.3.0
|
json_annotation: 4.9.0
|
||||||
simple_gesture_detector: 0.2.0
|
dynamic_color: 1.7.0
|
||||||
json_annotation: 4.4.0
|
intl: 0.19.0
|
||||||
|
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.1.7
|
build_runner: 2.4.13
|
||||||
built_value_generator: 8.1.4
|
built_value_generator: 8.9.2
|
||||||
json_serializable: 6.1.4
|
json_serializable: 6.8.0
|
||||||
|
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
|
||||||
|
|
38
renovate.json5
Normal file
38
renovate.json5
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"$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