Compare commits

..

No commits in common. "master" and "1.6.1+19" have entirely different histories.

36 changed files with 410 additions and 880 deletions

21
.drone.yml Normal file
View file

@ -0,0 +1,21 @@
---
kind: pipeline
type: docker
name: build
trigger:
event:
include:
- push
- pull_request
steps:
- name: build
image: ghcr.io/cirruslabs/flutter:3.10.2
commands:
- flutter doctor
- flutter pub get
- flutter pub outdated
- flutter packages pub run build_runner build --delete-conflicting-outputs
- flutter analyze --no-pub --no-current-package lib/
- flutter build apk --debug

View file

@ -1,21 +0,0 @@
on: [ push ]
jobs:
build:
runs-on: docker
container:
image: ghcr.io/cirruslabs/flutter:3.24.4
steps:
- name: Prepare requirements
run: |
apt-get update
apt-get install -y nodejs npm git
rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v3
- name: Build
run: |
flutter doctor
flutter pub get
flutter pub outdated
flutter packages pub run build_runner build --delete-conflicting-outputs
flutter analyze --no-pub --no-current-package lib/
flutter build apk --debug

1
.gitignore vendored
View file

@ -1,7 +1,6 @@
# Miscellaneous # Miscellaneous
*.class *.class
*.lock *.lock
!Gemfile.lock
!pubspec.lock !pubspec.lock
*.log *.log
*.pyc *.pyc

View file

@ -1,20 +1,5 @@
# CHANGELOG # CHANGELOG
## 1.6.4+22 - 2024/11/01
* Dependency updates
* Internal build updates
## 1.6.3+21
* Fixed not receiving share requests from other applications
## 1.6.2+20
* Updated internal dependencies
* Moved progress indicator of _Show Configuration_ into the underlying button
* Bumped Android minSdk to `30` (Android 11)
* Bumped Android targetSdk to `34` (Android 14)
* Fixed permission service not handling Android SDK 33 correctly
* Fixed permission service not being started during application start
## 1.6.1+19 ## 1.6.1+19
* Updated internal dependencies * Updated internal dependencies

View file

@ -2,8 +2,7 @@
A mobile flutter app for [FileBin](https://git.server-speed.net/users/flo/filebin/). A mobile flutter app for [FileBin](https://git.server-speed.net/users/flo/filebin/).
Available on the [Play Store](https://play.google.com/store/apps/details?id=de.varakh.fbmobile) and Available on the [Play Store](https://play.google.com/store/apps/details?id=de.varakh.fbmobile).
[IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.varakh.fbmobile/).
The main git repository is hosted at **[https://git.myservermanager.com/varakh/fbmobile](https://git.myservermanager.com/varakh/fbmobile)**. The main git repository is hosted at **[https://git.myservermanager.com/varakh/fbmobile](https://git.myservermanager.com/varakh/fbmobile)**.
Other repositories are mirrors and pull requests, issues, and planning are managed there. Other repositories are mirrors and pull requests, issues, and planning are managed there.
@ -95,7 +94,7 @@ profiles. They're stored in a separate git repository and are encrypted.
You need access to the git repository in which those private files reside. You need access to the git repository in which those private files reside.
#### Usage / doing the actual release #### Usage
Go into the platform directory you want to build for, e.g. `ios/` or `android/` and then look into the Go into the platform directory you want to build for, e.g. `ios/` or `android/` and then look into the
`Fastlane` file which lanes are present. Run a lane via `fastlane <platform> <lane>`, e.g. use the `Fastlane` file which lanes are present. Run a lane via `fastlane <platform> <lane>`, e.g. use the
@ -105,37 +104,13 @@ following to build for Android `fastlane android build`.
##### Android ##### Android
It's recommended you set up `fastlane` via `bundler` (you need this to be installed on your machine). Use `fastlane android beta` to build and upload a new beta version to the Play Store.
Go into the `android/` sub-directory of the project
```shell
bundle config set --local path 'vendor/bundle'
bundle install
# update fastlane when needed
bundle update fastlane
# build only
bundle exec fastlane android build
# deploy (push BETA to app store)
bundle exec fastlane android beta
# deploy (push to app store)
bundle exec fastlane android deploy
# deploy (build signed fdroid large bundle [no target and abi split])
bundle exec fastlane android build_production_fdroid
```
##### iOS ##### iOS
For iOS you need to execute `fastlane ios build` before uploading to testflight with For iOS you need to execute `fastlane ios build` before uploading to testflight with
`fastlane ios beta`. `fastlane ios beta`.
Probably do the same Ruby/fastlane setup as mentioned under the _Android_ section.
### Release manually (not recommended) ### Release manually (not recommended)
See the following links on how to setup: See the following links on how to setup:

2
android/.gitignore vendored
View file

@ -5,5 +5,3 @@ gradle-wrapper.jar
/gradlew.bat /gradlew.bat
/local.properties /local.properties
GeneratedPluginRegistrant.java GeneratedPluginRegistrant.java
.bundle
vendor/

View file

@ -1,222 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.7)
base64
nkf
rexml
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.1000.0)
aws-sdk-core (3.211.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.95.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.169.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.10.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.112.0)
faraday (1.10.4)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.3.1)
fastlane (2.225.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, < 2.0.0)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.5)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.3)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.7.1)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.4.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.31.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.8.1)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.7)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.7.5)
jwt (2.9.3)
base64
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.1)
nanaimo (0.4.0)
naturally (2.2.1)
nkf (0.2.0)
optparse (0.5.0)
os (1.1.4)
plist (3.7.1)
public_suffix (6.0.1)
rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.3.9)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
naturally
sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unicode-display_width (2.6.0)
word_wrap (1.0.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
fastlane
BUNDLED WITH
2.5.16

View file

@ -1,9 +1,3 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@ -12,6 +6,11 @@ if (localPropertiesFile.exists()) {
} }
} }
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@ -22,6 +21,9 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
@ -29,9 +31,7 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 34 compileSdkVersion 33
namespace "de.varakh.fbmobile"
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
@ -39,8 +39,8 @@ android {
defaultConfig { defaultConfig {
applicationId "de.varakh.fbmobile" applicationId "de.varakh.fbmobile"
minSdkVersion 30 minSdkVersion 19
targetSdkVersion 34 targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View file

@ -4,10 +4,7 @@
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<queries> <queries>
<intent> <intent>

View file

@ -21,41 +21,6 @@
<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>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
@ -65,10 +30,7 @@
</application> </application>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<queries> <queries>
<intent> <intent>

View file

@ -4,10 +4,7 @@
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<queries> <queries>
<intent> <intent>

View file

@ -1,3 +1,14 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
}
}
allprojects { allprojects {
repositories { repositories {
google() google()

View file

@ -11,11 +11,6 @@ platform :android do
sh("#{ENV['PWD']}/fastlane/buildAndroidProduction.sh") sh("#{ENV['PWD']}/fastlane/buildAndroidProduction.sh")
end end
desc "Build Production fdroid"
lane :build_production_fdroid do
sh("#{ENV['PWD']}/fastlane/buildAndroidProductionFdroid.sh")
end
desc "Build" desc "Build"
lane :build do lane :build do
sh("#{ENV['PWD']}/fastlane/buildAndroid.sh") sh("#{ENV['PWD']}/fastlane/buildAndroid.sh")

View file

@ -31,14 +31,6 @@ Build Debug
Build Production Build Production
### android build_production_fdroid
```sh
[bundle exec] fastlane android build_production_fdroid
```
Build Production fdroid
### android build ### android build
```sh ```sh

View file

@ -1,9 +0,0 @@
#!/usr/bin/env sh
cd ../../;
flutter clean && \
flutter pub get &&
flutter packages pub run build_runner build --delete-conflicting-outputs;
flutter build apk --release;
flutter build apk --split-per-abi --release;

View file

@ -1,6 +1,4 @@
agpVersion=8.7.2
kotlinVersion=1.7.10
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true

View file

@ -1,7 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip

View file

@ -1,25 +1,15 @@
pluginManagement { include ':app'
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
repositories { def plugins = new Properties()
google() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
mavenCentral() if (pluginsFile.exists()) {
gradlePluginPortal() pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
} }
plugins { plugins.each { name, path ->
id "dev.flutter.flutter-plugin-loader" version "1.0.0" def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
id "com.android.application" version "${agpVersion}" apply false include ":$name"
id "org.jetbrains.kotlin.android" version "${kotlinVersion}" apply false project(":$name").projectDir = pluginDirectory
} }
include ":app"

View file

@ -126,6 +126,14 @@
"description": "Could not open '{link}'. Please ensure that you have an application installed which handles opening such link types." "description": "Could not open '{link}'. Please ensure that you have an application installed which handles opening such link types."
} }
}, },
"permission_service": {
"dialog": {
"title": "Storage permission",
"description": "Storage permission should be granted to the app so that it can work properly. Do you want to grant permission or ignore this message permanently in the future?",
"grant": "Grant",
"ignore": "Ignore"
}
},
"dialog": { "dialog": {
"confirm": "OK", "confirm": "OK",
"cancel": "Cancel" "cancel": "Cancel"

View file

@ -5,8 +5,8 @@ class RestServiceException extends ServiceException {
final int statusCode; final int statusCode;
final dynamic responseBody; final dynamic responseBody;
RestServiceException(this.statusCode, {this.responseBody, super.message = null}) RestServiceException(this.statusCode, {this.responseBody, String? message})
: super(code: ErrorCode.restError); : super(code: ErrorCode.restError, message: message);
@override @override
String toString() { String toString() {

View file

@ -8,7 +8,7 @@ import '../services/dialog_service.dart';
class DialogManager extends StatefulWidget { class DialogManager extends StatefulWidget {
final Widget? child; final Widget? child;
const DialogManager({super.key, this.child}); const DialogManager({Key? key, this.child}) : super(key: key);
@override @override
_DialogManagerState createState() => _DialogManagerState(); _DialogManagerState createState() => _DialogManagerState();

View file

@ -11,7 +11,7 @@ import '../util/logger.dart';
class LifeCycleManager extends StatefulWidget { class LifeCycleManager extends StatefulWidget {
final Widget? child; final Widget? child;
const LifeCycleManager({super.key, this.child}); const LifeCycleManager({Key? key, this.child}) : super(key: key);
@override @override
_LifeCycleManagerState createState() => _LifeCycleManagerState(); _LifeCycleManagerState createState() => _LifeCycleManagerState();

View file

@ -1,100 +1,108 @@
import 'dart:async'; import 'dart:async';
import 'dart:io' show Platform;
import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter_translate/flutter_translate.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import '../../constants.dart'; import '../../constants.dart';
import '../../core/datamodels/dialog_response.dart';
import '../../core/services/dialog_service.dart';
import '../../core/services/stoppable_service.dart'; import '../../core/services/stoppable_service.dart';
import '../../core/util/logger.dart'; import '../../core/util/logger.dart';
import '../../locator.dart';
import 'storage_service.dart';
class PermissionService extends StoppableService { class PermissionService extends StoppableService {
final Logger _logger = getLogger(); final Logger _logger = getLogger();
final DialogService _dialogService = locator<DialogService>();
final StorageService _storageService = locator<StorageService>();
Timer? _serviceCheckTimer; Timer? _serviceCheckTimer;
PermissionStatus? _permissionStatus;
bool _permanentlyIgnored = false;
bool _devicePermissionDialogActive = false; bool _devicePermissionDialogActive = false;
bool _ownPermissionDialogActive = false;
bool _deviceInformationInitialized = false; PermissionService() {
bool _useStoragePermission = true; _devicePermissionDialogActive = true;
PermissionService(); Permission.storage.request().then((status) {
_permissionStatus = status;
if (PermissionStatus.permanentlyDenied == status) {
_permanentlyIgnored = true;
}
}).whenComplete(() {
_logger.d('Initial device request permission finished');
_devicePermissionDialogActive = false;
});
}
Future checkEnabledAndPermission() async { Future checkEnabledAndPermission() async {
if (_permanentlyIgnored) {
await _storageService.storeStoragePermissionDialogIgnored();
_permanentlyIgnored = false;
_logger.d('Set permanently ignored permission request');
stop();
}
if (_devicePermissionDialogActive) { if (_devicePermissionDialogActive) {
_logger.d('Device permission dialog active, skipping'); _logger.d('Device permission dialog active, skipping');
return; return;
} }
bool allGranted = false; if (_ownPermissionDialogActive) {
bool anyPermanentlyDenied = false; _logger.d('Own permission dialog already active, skipping');
return;
// Since Android compileSdk >= 33, "storage" is deprecated
// 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;
} }
// show warning to user to manually handle, don't enforce it over and over again var ignoredDialog =
if (anyPermanentlyDenied) { await _storageService.hasStoragePermissionDialogIgnored();
_logger.w(
"At least one required permission has been denied permanently, stopping service"); if (ignoredDialog) {
_logger.d('Permanently ignored permission request, skipping');
stop(); stop();
return; return;
} }
// all good, stop the permission service _permissionStatus = await Permission.storage.status;
if (allGranted) { if (_permissionStatus != PermissionStatus.granted) {
_logger.d("All permissions have been granted, stopping service"); if (_permissionStatus == PermissionStatus.permanentlyDenied) {
stop(); await _storageService.storeStoragePermissionDialogIgnored();
return; return;
} }
// not all have been granted, show OS dialog _ownPermissionDialogActive = true;
_logger.d( DialogResponse response = await _dialogService.showConfirmationDialog(
"Not all permissions have been granted yet, initializing permission dialog"); title: translate('permission_service.dialog.title'),
_devicePermissionDialogActive = true; description: translate('permission_service.dialog.description'),
buttonTitleAccept: translate('permission_service.dialog.grant'),
buttonTitleDeny: translate('permission_service.dialog.ignore'));
if (_useStoragePermission) { if (!response.confirmed!) {
await [Permission.storage].request().whenComplete(() { await _storageService.storeStoragePermissionDialogIgnored();
_logger.d('Device request permission finished'); } else {
_devicePermissionDialogActive = false; _devicePermissionDialogActive = true;
}); Permission.storage.request().then((status) async {
if (PermissionStatus.permanentlyDenied == status) {
await _storageService.storeStoragePermissionDialogIgnored();
}
}).whenComplete(() {
_logger.d('Device request permission finished');
_devicePermissionDialogActive = false;
});
}
_ownPermissionDialogActive = false;
} else { } else {
await [Permission.photos, Permission.videos, Permission.audio] await _storageService.storeStoragePermissionDialogIgnored();
.request()
.whenComplete(() {
_logger.d('Device request permission finished');
_devicePermissionDialogActive = false;
});
} }
} }
@override @override
Future start() async { Future start() async {
super.start(); super.start();
await _determineDeviceInfo();
await checkEnabledAndPermission(); await checkEnabledAndPermission();
_serviceCheckTimer = Timer.periodic( _serviceCheckTimer = Timer.periodic(
@ -116,29 +124,6 @@ class PermissionService extends StoppableService {
_logger.d('PermissionService stopped'); _logger.d('PermissionService stopped');
} }
Future _determineDeviceInfo() async {
if (_deviceInformationInitialized) {
_logger.d('Device information already initialized, skipping');
return;
}
DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
if (Platform.isAndroid) {
final androidInfo = await deviceInfoPlugin.androidInfo;
if (androidInfo.version.sdkInt >= 33) {
_useStoragePermission = false;
}
}
if (_useStoragePermission) {
_logger.d('Device requires [storage] permission');
} else {
_logger.d('Device requires [photos,videos,audio] permission');
}
_deviceInformationInitialized = true;
}
void _removeServiceCheckTimer() { void _removeServiceCheckTimer() {
if (_serviceCheckTimer != null) { if (_serviceCheckTimer != null) {
_serviceCheckTimer!.cancel(); _serviceCheckTimer!.cancel();

View file

@ -7,6 +7,8 @@ import '../models/session.dart';
class StorageService { class StorageService {
static const _sessionKey = 'session'; static const _sessionKey = 'session';
static const _lastUrlKey = 'last_url'; static const _lastUrlKey = 'last_url';
static const _storagePermissionDialogIgnoredKey =
'storage_permission_ignored';
Future<bool> storeLastUrl(String url) { Future<bool> storeLastUrl(String url) {
return _store(_lastUrlKey, url); return _store(_lastUrlKey, url);
@ -37,6 +39,14 @@ class StorageService {
return _remove(_sessionKey); return _remove(_sessionKey);
} }
Future<bool> storeStoragePermissionDialogIgnored() {
return _store(_storagePermissionDialogIgnoredKey, true.toString());
}
Future<bool> hasStoragePermissionDialogIgnored() {
return _exists(_storagePermissionDialogIgnoredKey);
}
Future<bool> _exists(String key) async { Future<bool> _exists(String key) async {
final SharedPreferences prefs = await SharedPreferences.getInstance(); final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.containsKey(key); return prefs.containsKey(key);

View file

@ -108,7 +108,7 @@ class HistoryModel extends BaseModel {
} else { } else {
errorMessage = translate('app.unknown_error'); errorMessage = translate('app.unknown_error');
setStateView(ViewState.idle); setStateView(ViewState.idle);
_logger.e('An unknown error occurred', error: e); _logger.e('An unknown error occurred', e);
rethrow; rethrow;
} }
} }
@ -156,7 +156,7 @@ class HistoryModel extends BaseModel {
} else { } else {
errorMessage = translate('app.unknown_error'); errorMessage = translate('app.unknown_error');
setStateView(ViewState.idle); setStateView(ViewState.idle);
_logger.e('An unknown error occurred', error: e); _logger.e('An unknown error occurred', e);
rethrow; rethrow;
} }
} }

View file

@ -163,7 +163,7 @@ class LoginModel extends BaseModel {
errorMessage = translate('app.unknown_error'); errorMessage = translate('app.unknown_error');
_sessionService.logout(); _sessionService.logout();
setStateView(ViewState.idle); setStateView(ViewState.idle);
_logger.e('An unknown error occurred', error: e); _logger.e('An unknown error occurred', e);
rethrow; rethrow;
} }

View file

@ -77,7 +77,7 @@ class ProfileModel extends BaseModel {
setStateBoolValue(_configurationButtonLoading, false); setStateBoolValue(_configurationButtonLoading, false);
_sessionService.logout(); _sessionService.logout();
setStateBoolValue(_configurationButtonLoading, false); setStateBoolValue(_configurationButtonLoading, false);
_logger.e('An unknown error occurred', error: e); _logger.e('An unknown error occurred', e);
rethrow; rethrow;
} }
} }

View file

@ -1,4 +1,3 @@
import 'package:fbmobile/core/services/permission_service.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import '../../locator.dart'; import '../../locator.dart';
@ -10,18 +9,16 @@ import 'base_model.dart';
class StartUpViewModel extends BaseModel { class StartUpViewModel extends BaseModel {
final SessionService _sessionService = locator<SessionService>(); final SessionService _sessionService = locator<SessionService>();
final PermissionService _permissionService = locator<PermissionService>();
final NavigationService _navigationService = locator<NavigationService>(); final NavigationService _navigationService = locator<NavigationService>();
Future handleStartUpLogic() async { Future handleStartUpLogic() async {
setStateView(ViewState.busy); setStateView(ViewState.busy);
setStateMessage(translate('startup.init')); setStateMessage(translate('startup.init'));
await Future.delayed(const Duration(milliseconds: 100)); await Future.delayed(const Duration(milliseconds: 150));
setStateMessage(translate('startup.start_services')); setStateMessage(translate('startup.start_services'));
await _sessionService.start(); await _sessionService.start();
await _permissionService.start(); await Future.delayed(const Duration(milliseconds: 150));
await Future.delayed(const Duration(milliseconds: 100));
_navigationService.navigateAndReplaceTo(HomeView.routeName); _navigationService.navigateAndReplaceTo(HomeView.routeName);

View file

@ -152,9 +152,9 @@ class UploadModel extends BaseModel {
)) ))
?.files; ?.files;
} on PlatformException catch (e) { } on PlatformException catch (e) {
_logger.e('Unsupported operation', error: e); _logger.e('Unsupported operation', e);
} catch (ex) { } catch (ex) {
_logger.e('An unknown error occurred', error: ex); _logger.e('An unknown error occurred', ex);
} }
loadingPath = false; loadingPath = false;
@ -238,7 +238,7 @@ class UploadModel extends BaseModel {
errorMessage = translate('app.unknown_error'); errorMessage = translate('app.unknown_error');
setStateMessage(null); setStateMessage(null);
setStateView(ViewState.idle); setStateView(ViewState.idle);
_logger.e('An unknown error occurred', error: e); _logger.e('An unknown error occurred', e);
rethrow; rethrow;
} }
} }

View file

@ -91,9 +91,7 @@ class HistoryView extends StatelessWidget {
content: Text(translate('history.copy_link.copied')), content: Text(translate('history.copy_link.copied')),
duration: const Duration(seconds: 10), duration: const Duration(seconds: 10),
); );
if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(snackBar);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}); });
})); }));

View file

@ -53,26 +53,24 @@ class ProfileView extends StatelessWidget {
UIHelper.verticalSpaceMedium(), UIHelper.verticalSpaceMedium(),
Padding( Padding(
padding: const EdgeInsets.only(left: 25.0, right: 25.0), padding: const EdgeInsets.only(left: 25.0, right: 25.0),
child: ElevatedButton.icon( child: model.configLoading
icon: model.configLoading ? Center(
? Container( child: Column(
width: 24, mainAxisAlignment: MainAxisAlignment.center,
height: 24, crossAxisAlignment: CrossAxisAlignment.center,
padding: const EdgeInsets.all(2.0), children: [
child: const CircularProgressIndicator( const CircularProgressIndicator(),
color: blueColor, Text(translate('profile.show_config_loading')),
strokeWidth: 3, ],
), ))
) : ElevatedButton.icon(
: const Icon(Icons.settings, color: blueColor), icon: const Icon(Icons.settings, color: blueColor),
label: Text( label: Text(
model.configLoading translate('profile.show_config'),
? translate('profile.show_config_loading') ),
: translate('profile.show_config'), onPressed: () async {
), await model.showConfig(url);
onPressed: () async { })),
await model.showConfig(url);
})),
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),

View file

@ -151,10 +151,8 @@ class UploadView extends StatelessWidget {
duration: duration:
const Duration(seconds: 10), const Duration(seconds: 10),
); );
if (context.mounted) { ScaffoldMessenger.of(context)
ScaffoldMessenger.of(context) .showSnackBar(snackBar);
.showSnackBar(snackBar);
}
}); });
} }
}, },

View file

@ -7,11 +7,12 @@ class MyAppBar extends AppBar {
static final List<Widget> aboutDisabledWidgets = []; static final List<Widget> aboutDisabledWidgets = [];
MyAppBar( MyAppBar(
{super.key, {Key? 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));

File diff suppressed because it is too large Load diff

View file

@ -11,47 +11,46 @@ description: A mobile client for FileBin.
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.6.4+22 version: 1.6.1+19
environment: environment:
sdk: '>=3.5.0 <4.0.0' sdk: '>=2.19.4 <3.0.0'
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: 1.0.8 cupertino_icons: 1.0.5
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_translate: 4.1.0 flutter_translate: 4.0.4
provider: 6.1.2 provider: 6.0.5
stacked: 3.4.3 stacked: 3.3.0
get_it: 7.7.0 get_it: 7.6.0
logger: 2.4.0 logger: 1.3.0
shared_preferences: 2.3.2 shared_preferences: 2.1.1
http: 1.2.2 http: 0.13.6
validators: 3.0.0 validators: 3.0.0
flutter_linkify: 6.0.0 flutter_linkify: 6.0.0
url_launcher: 6.3.1 url_launcher: 6.1.11
expandable: 5.0.1 expandable: 5.0.1
share_plus: 10.1.1 share_plus: 7.0.1
file_picker: 8.1.3 file_picker: 5.3.1
clipboard: 0.1.3 clipboard: 0.1.3
permission_handler: 11.3.1 permission_handler: 10.2.0
package_info_plus: 8.1.0 package_info_plus: 4.0.1
json_annotation: 4.9.0 json_annotation: ^4.8.1
dynamic_color: 1.7.0 dynamic_color: 1.6.5
intl: 0.19.0 intl: 0.18.0
path: 1.9.0 path: 1.8.3
flutter_sharing_intent: 1.1.1 flutter_sharing_intent: 1.0.6
device_info_plus: 11.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: 2.4.13 build_runner: 2.4.4
built_value_generator: 8.9.2 built_value_generator: 8.6.0
json_serializable: 6.8.0 json_serializable: 6.7.0
flutter_lints: 5.0.0 flutter_lints: 2.0.1
# 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

View file

@ -1,38 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":rebaseStalePrs",
":ignoreUnstable",
"group:monorepos",
"group:recommended"
],
"prConcurrentLimit": 0,
"schedule": [
"monthly"
],
"ignorePaths": [
"android/**",
"ios/**"
],
"ignoreDeps": [
"intl",
"path"
],
"packageRules": [
{
"matchUpdateTypes": [
"minor"
],
"groupName": "all minor dependencies",
"groupSlug": "all-minor-deps"
},
{
"matchUpdateTypes": [
"patch"
],
"groupName": "all patch dependencies",
"groupSlug": "all-patch-deps"
}
]
}