From 2023a528b3d88b4900289a3c7271a07f7a8d59fa Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 18 May 2026 10:26:44 +0200 Subject: [PATCH 01/11] fix: allow nullable version and versionNumber in PiServerResponse --- lib/model/pi_server_response.dart | 19 +++++++++++-------- lib/model/pi_server_response.freezed.dart | 16 ++++++++-------- .../settings_notifier.g.dart | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/model/pi_server_response.dart b/lib/model/pi_server_response.dart index b0016a19..796f1efd 100644 --- a/lib/model/pi_server_response.dart +++ b/lib/model/pi_server_response.dart @@ -53,8 +53,8 @@ sealed class PiServerResponse< required String jsonrpc, required PiServerResult result, required double time, - required String version, - required String versionNumber, + required String? version, + required String? versionNumber, @Default(null) String? signature, @Default(null) D? detail, }) = PiSuccessResponse; @@ -71,8 +71,8 @@ sealed class PiServerResponse< /// This is a throwable error required PiServerResultError piServerResultError, required double time, - required String version, - required String versionNumber, + required String? version, + required String? versionNumber, @Default(null) String? signature, }) = PiErrorResponse; bool get isError => this is PiErrorResponse; @@ -91,8 +91,11 @@ sealed class PiServerResponse< JSONRPC: Validators.string, RESULT: RequiredObjectValidator>(), TIME: RequiredObjectValidator(), - VERSION: RequiredObjectValidator( - allowedValues: (v) => v.contains(' '), + VERSION: OptionalObjectValidator( + transformer: (v) { + final s = v as String; + return s.contains(' ') ? s : null; + }, ), VERSION_NUMBER: Validators.stringOptional, DETAIL: OptionalObjectValidator(), @@ -108,10 +111,10 @@ sealed class PiServerResponse< map[RESULT] as Map, ), time: map[TIME] as double, - version: map[VERSION] as String, + version: map[VERSION] as String?, versionNumber: map[VERSION_NUMBER] as String? ?? - (map[VERSION] as String).split(' ')[1], + (map[VERSION] as String?)?.split(' ')[1], detail: PiServerResultDetail.fromResultDetail(map[DETAIL]), signature: map[SIGNATURE] as String?, ); diff --git a/lib/model/pi_server_response.freezed.dart b/lib/model/pi_server_response.freezed.dart index 2b012563..f058d0a8 100644 --- a/lib/model/pi_server_response.freezed.dart +++ b/lib/model/pi_server_response.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$PiServerResponse { - int get statusCode; int get id; String get jsonrpc; double get time; String get version; String get versionNumber; String? get signature; D? get detail; + int get statusCode; int get id; String get jsonrpc; double get time; String? get version; String? get versionNumber; String? get signature; D? get detail; @@ -116,7 +116,7 @@ return error(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen({TResult Function( int statusCode, int id, String jsonrpc, PiServerResult result, double time, String version, String versionNumber, String? signature, D? detail)? success,TResult Function( int statusCode, int id, String jsonrpc, D? detail, PiServerResultError piServerResultError, double time, String version, String versionNumber, String? signature)? error,required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen({TResult Function( int statusCode, int id, String jsonrpc, PiServerResult result, double time, String? version, String? versionNumber, String? signature, D? detail)? success,TResult Function( int statusCode, int id, String jsonrpc, D? detail, PiServerResultError piServerResultError, double time, String? version, String? versionNumber, String? signature)? error,required TResult orElse(),}) {final _that = this; switch (_that) { case PiSuccessResponse() when success != null: return success(_that.statusCode,_that.id,_that.jsonrpc,_that.result,_that.time,_that.version,_that.versionNumber,_that.signature,_that.detail);case PiErrorResponse() when error != null: @@ -138,7 +138,7 @@ return error(_that.statusCode,_that.id,_that.jsonrpc,_that.detail,_that.piServer /// } /// ``` -@optionalTypeArgs TResult when({required TResult Function( int statusCode, int id, String jsonrpc, PiServerResult result, double time, String version, String versionNumber, String? signature, D? detail) success,required TResult Function( int statusCode, int id, String jsonrpc, D? detail, PiServerResultError piServerResultError, double time, String version, String versionNumber, String? signature) error,}) {final _that = this; +@optionalTypeArgs TResult when({required TResult Function( int statusCode, int id, String jsonrpc, PiServerResult result, double time, String? version, String? versionNumber, String? signature, D? detail) success,required TResult Function( int statusCode, int id, String jsonrpc, D? detail, PiServerResultError piServerResultError, double time, String? version, String? versionNumber, String? signature) error,}) {final _that = this; switch (_that) { case PiSuccessResponse(): return success(_that.statusCode,_that.id,_that.jsonrpc,_that.result,_that.time,_that.version,_that.versionNumber,_that.signature,_that.detail);case PiErrorResponse(): @@ -156,7 +156,7 @@ return error(_that.statusCode,_that.id,_that.jsonrpc,_that.detail,_that.piServer /// } /// ``` -@optionalTypeArgs TResult? whenOrNull({TResult? Function( int statusCode, int id, String jsonrpc, PiServerResult result, double time, String version, String versionNumber, String? signature, D? detail)? success,TResult? Function( int statusCode, int id, String jsonrpc, D? detail, PiServerResultError piServerResultError, double time, String version, String versionNumber, String? signature)? error,}) {final _that = this; +@optionalTypeArgs TResult? whenOrNull({TResult? Function( int statusCode, int id, String jsonrpc, PiServerResult result, double time, String? version, String? versionNumber, String? signature, D? detail)? success,TResult? Function( int statusCode, int id, String jsonrpc, D? detail, PiServerResultError piServerResultError, double time, String? version, String? versionNumber, String? signature)? error,}) {final _that = this; switch (_that) { case PiSuccessResponse() when success != null: return success(_that.statusCode,_that.id,_that.jsonrpc,_that.result,_that.time,_that.version,_that.versionNumber,_that.signature,_that.detail);case PiErrorResponse() when error != null: @@ -180,8 +180,8 @@ class PiSuccessResponse result; @override final double time; -@override final String version; -@override final String versionNumber; +@override final String? version; +@override final String? versionNumber; @override@JsonKey() final String? signature; @override@JsonKey() final D? detail; @@ -222,8 +222,8 @@ class PiErrorResponse r'153f57fa9a5b365e5af6159082baaf95e9af5d76'; +String _$settingsNotifierHash() => r'2cb678680513b175f42900cebb9d12bacd88853b'; final class SettingsNotifierFamily extends $Family with From fbb25a0d83ef90b69f90fa50b6cdfbf2ada1c459 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 18 May 2026 12:13:27 +0200 Subject: [PATCH 02/11] fix: allow nullable fields for id, jsonrpc, time, version, and versionNumber in PiServerResponse --- .gitignore | 1 + lib/model/pi_server_response.dart | 32 +++++++++++------------ lib/model/pi_server_response.freezed.dart | 32 +++++++++++------------ 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index fea212de..5860e8cc 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ .buildlog/ .history .svn/ +*.local.* # IntelliJ related *.iml diff --git a/lib/model/pi_server_response.dart b/lib/model/pi_server_response.dart index 796f1efd..132d6508 100644 --- a/lib/model/pi_server_response.dart +++ b/lib/model/pi_server_response.dart @@ -49,12 +49,12 @@ sealed class PiServerResponse< const PiServerResponse._(); factory PiServerResponse.success({ required int statusCode, - required int id, - required String jsonrpc, + @Default(null) int? id, + @Default(null) String? jsonrpc, required PiServerResult result, - required double time, - required String? version, - required String? versionNumber, + @Default(null) double? time, + @Default(null) String? version, + @Default(null) String? versionNumber, @Default(null) String? signature, @Default(null) D? detail, }) = PiSuccessResponse; @@ -64,15 +64,15 @@ sealed class PiServerResponse< factory PiServerResponse.error({ required int statusCode, - required int id, - required String jsonrpc, + @Default(null) int? id, + @Default(null) String? jsonrpc, @Default(null) D? detail, /// This is a throwable error required PiServerResultError piServerResultError, - required double time, - required String? version, - required String? versionNumber, + @Default(null) double? time, + @Default(null) String? version, + @Default(null) String? versionNumber, @Default(null) String? signature, }) = PiErrorResponse; bool get isError => this is PiErrorResponse; @@ -87,10 +87,10 @@ sealed class PiServerResponse< final map = validateMap( map: json, validators: { - ID: Validators.intType, - JSONRPC: Validators.string, + ID: Validators.intOptional, + JSONRPC: Validators.stringOptional, RESULT: RequiredObjectValidator>(), - TIME: RequiredObjectValidator(), + TIME: OptionalObjectValidator(), VERSION: OptionalObjectValidator( transformer: (v) { final s = v as String; @@ -105,12 +105,12 @@ sealed class PiServerResponse< ); return PiServerResponse.success( statusCode: statusCode, - id: map[ID] as int, - jsonrpc: map[JSONRPC] as String, + id: map[ID] as int?, + jsonrpc: map[JSONRPC] as String?, result: PiServerResult.fromResultMap( map[RESULT] as Map, ), - time: map[TIME] as double, + time: map[TIME] as double?, version: map[VERSION] as String?, versionNumber: map[VERSION_NUMBER] as String? ?? diff --git a/lib/model/pi_server_response.freezed.dart b/lib/model/pi_server_response.freezed.dart index f058d0a8..54f79892 100644 --- a/lib/model/pi_server_response.freezed.dart +++ b/lib/model/pi_server_response.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$PiServerResponse { - int get statusCode; int get id; String get jsonrpc; double get time; String? get version; String? get versionNumber; String? get signature; D? get detail; + int get statusCode; int? get id; String? get jsonrpc; double? get time; String? get version; String? get versionNumber; String? get signature; D? get detail; @@ -116,7 +116,7 @@ return error(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen({TResult Function( int statusCode, int id, String jsonrpc, PiServerResult result, double time, String? version, String? versionNumber, String? signature, D? detail)? success,TResult Function( int statusCode, int id, String jsonrpc, D? detail, PiServerResultError piServerResultError, double time, String? version, String? versionNumber, String? signature)? error,required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen({TResult Function( int statusCode, int? id, String? jsonrpc, PiServerResult result, double? time, String? version, String? versionNumber, String? signature, D? detail)? success,TResult Function( int statusCode, int? id, String? jsonrpc, D? detail, PiServerResultError piServerResultError, double? time, String? version, String? versionNumber, String? signature)? error,required TResult orElse(),}) {final _that = this; switch (_that) { case PiSuccessResponse() when success != null: return success(_that.statusCode,_that.id,_that.jsonrpc,_that.result,_that.time,_that.version,_that.versionNumber,_that.signature,_that.detail);case PiErrorResponse() when error != null: @@ -138,7 +138,7 @@ return error(_that.statusCode,_that.id,_that.jsonrpc,_that.detail,_that.piServer /// } /// ``` -@optionalTypeArgs TResult when({required TResult Function( int statusCode, int id, String jsonrpc, PiServerResult result, double time, String? version, String? versionNumber, String? signature, D? detail) success,required TResult Function( int statusCode, int id, String jsonrpc, D? detail, PiServerResultError piServerResultError, double time, String? version, String? versionNumber, String? signature) error,}) {final _that = this; +@optionalTypeArgs TResult when({required TResult Function( int statusCode, int? id, String? jsonrpc, PiServerResult result, double? time, String? version, String? versionNumber, String? signature, D? detail) success,required TResult Function( int statusCode, int? id, String? jsonrpc, D? detail, PiServerResultError piServerResultError, double? time, String? version, String? versionNumber, String? signature) error,}) {final _that = this; switch (_that) { case PiSuccessResponse(): return success(_that.statusCode,_that.id,_that.jsonrpc,_that.result,_that.time,_that.version,_that.versionNumber,_that.signature,_that.detail);case PiErrorResponse(): @@ -156,7 +156,7 @@ return error(_that.statusCode,_that.id,_that.jsonrpc,_that.detail,_that.piServer /// } /// ``` -@optionalTypeArgs TResult? whenOrNull({TResult? Function( int statusCode, int id, String jsonrpc, PiServerResult result, double time, String? version, String? versionNumber, String? signature, D? detail)? success,TResult? Function( int statusCode, int id, String jsonrpc, D? detail, PiServerResultError piServerResultError, double time, String? version, String? versionNumber, String? signature)? error,}) {final _that = this; +@optionalTypeArgs TResult? whenOrNull({TResult? Function( int statusCode, int? id, String? jsonrpc, PiServerResult result, double? time, String? version, String? versionNumber, String? signature, D? detail)? success,TResult? Function( int statusCode, int? id, String? jsonrpc, D? detail, PiServerResultError piServerResultError, double? time, String? version, String? versionNumber, String? signature)? error,}) {final _that = this; switch (_that) { case PiSuccessResponse() when success != null: return success(_that.statusCode,_that.id,_that.jsonrpc,_that.result,_that.time,_that.version,_that.versionNumber,_that.signature,_that.detail);case PiErrorResponse() when error != null: @@ -172,16 +172,16 @@ return error(_that.statusCode,_that.id,_that.jsonrpc,_that.detail,_that.piServer class PiSuccessResponse extends PiServerResponse { - PiSuccessResponse({required this.statusCode, required this.id, required this.jsonrpc, required this.result, required this.time, required this.version, required this.versionNumber, this.signature = null, this.detail = null}): super._(); + PiSuccessResponse({required this.statusCode, this.id = null, this.jsonrpc = null, required this.result, this.time = null, this.version = null, this.versionNumber = null, this.signature = null, this.detail = null}): super._(); @override final int statusCode; -@override final int id; -@override final String jsonrpc; +@override@JsonKey() final int? id; +@override@JsonKey() final String? jsonrpc; final PiServerResult result; -@override final double time; -@override final String? version; -@override final String? versionNumber; +@override@JsonKey() final double? time; +@override@JsonKey() final String? version; +@override@JsonKey() final String? versionNumber; @override@JsonKey() final String? signature; @override@JsonKey() final D? detail; @@ -212,18 +212,18 @@ String toString() { class PiErrorResponse extends PiServerResponse { - PiErrorResponse({required this.statusCode, required this.id, required this.jsonrpc, this.detail = null, required this.piServerResultError, required this.time, required this.version, required this.versionNumber, this.signature = null}): super._(); + PiErrorResponse({required this.statusCode, this.id = null, this.jsonrpc = null, this.detail = null, required this.piServerResultError, this.time = null, this.version = null, this.versionNumber = null, this.signature = null}): super._(); @override final int statusCode; -@override final int id; -@override final String jsonrpc; +@override@JsonKey() final int? id; +@override@JsonKey() final String? jsonrpc; @override@JsonKey() final D? detail; /// This is a throwable error final PiServerResultError piServerResultError; -@override final double time; -@override final String? version; -@override final String? versionNumber; +@override@JsonKey() final double? time; +@override@JsonKey() final String? version; +@override@JsonKey() final String? versionNumber; @override@JsonKey() final String? signature; From 18549944777801a6a83307de2738ca138bdd942c Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 19 May 2026 10:24:45 +0200 Subject: [PATCH 03/11] fix: update Flutter version to 3.44.0 in build and test workflows --- .github/workflows/flutter_build.yml | 4 ++-- .github/workflows/flutter_test.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/flutter_build.yml b/.github/workflows/flutter_build.yml index 3a02db39..8d97f8db 100644 --- a/.github/workflows/flutter_build.yml +++ b/.github/workflows/flutter_build.yml @@ -26,7 +26,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: "3.38.3" # Latest stable Flutter version as of Dec 05 2025 + flutter-version: "3.44.0" # Latest stable Flutter version as of May 19, 2026 - run: flutter clean - run: flutter --version @@ -53,7 +53,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: "3.38.3" # Latest stable Flutter version as of Dec 05 2025 + flutter-version: "3.44.0" # Latest stable Flutter version as of May 19, 2026 - name: Clean Flutter project run: flutter clean diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 8798b4b7..1f1be1cb 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -23,7 +23,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: "3.38.3" # Use the latest stable Flutter version as of Dec 05 2025 + flutter-version: "3.44.0" # Use the latest stable Flutter version as of May 19, 2026 cache: true # Enable caching for Flutter SDK itself # cache-key and cache-path are automatically handled by subosito/flutter-action when cache: true From 5ca53354251310e72b24e0980dd39e190c9d80a0 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 19 May 2026 10:30:31 +0200 Subject: [PATCH 04/11] fix: update dependencies in pubspec.lock and add builtInKotlin and newDsl flags in gradle.properties --- .../kotlin-compiler-12542241331089438549.salive | 0 android/gradle.properties | 6 +++++- pubspec.lock | 16 ++++++++-------- 3 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 android/.kotlin/sessions/kotlin-compiler-12542241331089438549.salive diff --git a/android/.kotlin/sessions/kotlin-compiler-12542241331089438549.salive b/android/.kotlin/sessions/kotlin-compiler-12542241331089438549.salive new file mode 100644 index 00000000..e69de29b diff --git a/android/gradle.properties b/android/gradle.properties index 2cf6e418..bb47bb9f 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -3,4 +3,8 @@ org.gradle.parallel=true org.gradle.caching=true android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/pubspec.lock b/pubspec.lock index c4207334..ec677840 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1152,10 +1152,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" mime: dependency: transitive description: @@ -1685,26 +1685,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7" + sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20" url: "https://pub.dev" source: hosted - version: "1.30.0" + version: "1.31.0" test_api: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" test_core: dependency: transitive description: name: test_core - sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51" + sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34" url: "https://pub.dev" source: hosted - version: "0.6.16" + version: "0.6.17" timezone: dependency: transitive description: From b0afca6ac17148ef80ab9455040dd2262375ba6b Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 19 May 2026 10:31:24 +0200 Subject: [PATCH 05/11] chore: add new Kotlin compiler session file --- .../.kotlin/sessions/kotlin-compiler-404227650117843940.salive | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 android/.kotlin/sessions/kotlin-compiler-404227650117843940.salive diff --git a/android/.kotlin/sessions/kotlin-compiler-404227650117843940.salive b/android/.kotlin/sessions/kotlin-compiler-404227650117843940.salive new file mode 100644 index 00000000..e69de29b From e64d23fbb30b69fe34b3a0f14181e82376934cc1 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 19 May 2026 11:20:30 +0200 Subject: [PATCH 06/11] refactor: remove unused dependencies and update Gradle configurations --- .gitignore | 1 + .../kotlin-compiler-12542241331089438549.salive | 0 .../kotlin-compiler-404227650117843940.salive | 0 android/app/build.gradle | 11 ++++++----- android/app/src/main/AndroidManifest.xml | 3 --- android/gradle.properties | 2 +- android/gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 4 ++-- .../transfer_container_action.dart | 3 +-- .../token_folder_expandable_header_icon.dart | 5 ++--- .../settings_groups/settings_group_general.dart | 4 ++-- pubspec.lock | 16 ---------------- pubspec.yaml | 2 -- 13 files changed, 16 insertions(+), 37 deletions(-) delete mode 100644 android/.kotlin/sessions/kotlin-compiler-12542241331089438549.salive delete mode 100644 android/.kotlin/sessions/kotlin-compiler-404227650117843940.salive diff --git a/.gitignore b/.gitignore index 5860e8cc..2a859423 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ build/ # Android related **/android/**/gradle-wrapper.jar **/android/.gradle +**/android/.kotlin/ **/android/captures/ **/android/gradlew **/android/gradlew.bat diff --git a/android/.kotlin/sessions/kotlin-compiler-12542241331089438549.salive b/android/.kotlin/sessions/kotlin-compiler-12542241331089438549.salive deleted file mode 100644 index e69de29b..00000000 diff --git a/android/.kotlin/sessions/kotlin-compiler-404227650117843940.salive b/android/.kotlin/sessions/kotlin-compiler-404227650117843940.salive deleted file mode 100644 index e69de29b..00000000 diff --git a/android/app/build.gradle b/android/app/build.gradle index 37aa22aa..04576601 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -3,7 +3,6 @@ plugins { // START: FlutterFire Configuration id 'com.google.gms.google-services' // END: FlutterFire Configuration - id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" } @@ -62,10 +61,6 @@ android { targetCompatibility = JavaVersion.VERSION_21 } - kotlinOptions { - jvmTarget = '21' - } - defaultConfig { applicationId "it.netknights.piauthenticator" minSdkVersion flutter.minSdkVersion @@ -140,3 +135,9 @@ android { flutter { source '../..' } + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21 + } +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 87e6da08..d74b5d60 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -26,9 +26,6 @@ android:icon="@mipmap/ic_launcher" android:enableOnBackInvokedCallback="true" android:requestLegacyExternalStorage="true"> - ()?.lockColor, diff --git a/lib/views/settings_view/settings_groups/settings_group_general.dart b/lib/views/settings_view/settings_groups/settings_group_general.dart index 06764aaf..d853c8c7 100644 --- a/lib/views/settings_view/settings_groups/settings_group_general.dart +++ b/lib/views/settings_view/settings_groups/settings_group_general.dart @@ -18,7 +18,7 @@ * limitations under the License. */ import 'package:flutter/material.dart'; -import 'package:simple_icons/simple_icons.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../l10n/app_localizations.dart'; @@ -67,7 +67,7 @@ class SettingsGroupGeneral extends StatelessWidget { style: Theme.of(context).textTheme.bodyMedium, maxLines: 2, ), - icon: const Icon(SimpleIcons.github), + icon: const FaIcon(FontAwesomeIcons.github), ), ], ); diff --git a/pubspec.lock b/pubspec.lock index ec677840..51417697 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1132,14 +1132,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.0" - material_design_icons_flutter: - dependency: "direct main" - description: - name: material_design_icons_flutter - sha256: "6f986b7a51f3ad4c00e33c5c84e8de1bdd140489bbcdc8b66fc1283dad4dea5a" - url: "https://pub.dev" - source: hosted - version: "7.0.7296" material_symbols_icons: dependency: "direct main" description: @@ -1572,14 +1564,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" - simple_icons: - dependency: "direct main" - description: - name: simple_icons - sha256: "2ca3cd79c9f12e97a8588cae0f342609f19fd2e82315356cb09b5c4987ad0808" - url: "https://pub.dev" - source: hosted - version: "14.6.1" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 99c329c9..997fba83 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,7 +70,6 @@ dependencies: local_auth_android: ^2.0.3 local_auth_darwin: ^2.0.1 logger: ^2.4.0 - material_design_icons_flutter: ^7.0.7296 material_symbols_icons: ^4.2906.0 mutex: ^3.1.0 no_screenshot: ^1.0.0 @@ -82,7 +81,6 @@ dependencies: protobuf: ^6.0.0 riverpod_annotation: ^4.0.0 shared_preferences: ^2.3.2 - simple_icons: ^14.6.1 url_launcher: ^6.3.1 uuid: ^4.5.1 zxing2: ^0.2.3 From 0323e0e8b14f2680113d2643bd371cd07eeedcb7 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 19 May 2026 11:30:27 +0200 Subject: [PATCH 07/11] fix: update Flutter build and test workflows to use latest dependencies and configurations --- .github/workflows/flutter_build.yml | 19 ++++++++----------- .github/workflows/flutter_test.yml | 13 +++++-------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.github/workflows/flutter_build.yml b/.github/workflows/flutter_build.yml index 8d97f8db..eb15c6d3 100644 --- a/.github/workflows/flutter_build.yml +++ b/.github/workflows/flutter_build.yml @@ -9,24 +9,24 @@ jobs: build_ios: name: (iOS) runs-on: macos-latest - timeout-minutes: 15 + timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set Xcode version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: "16.4" # Set to a specific recent stable Xcode version + xcode-version: "16.4" - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v4 with: distribution: "zulu" - java-version: "17" # Use JDK 17 for better compatibility + java-version: "21" - uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: "3.44.0" # Latest stable Flutter version as of May 19, 2026 + flutter-version: "3.44.0" - run: flutter clean - run: flutter --version @@ -39,7 +39,6 @@ jobs: name: (Android) runs-on: ubuntu-latest timeout-minutes: 20 - # Removed the 'strategy' block with 'api-level' and 'target' matrix steps: - uses: actions/checkout@v4 @@ -47,13 +46,13 @@ jobs: uses: actions/setup-java@v4 with: distribution: "zulu" - java-version: "21" # Keeping Java 21 as per your current setup + java-version: "21" - name: Set up Flutter SDK uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: "3.44.0" # Latest stable Flutter version as of May 19, 2026 + flutter-version: "3.44.0" - name: Clean Flutter project run: flutter clean @@ -70,7 +69,6 @@ jobs: - name: Generate localization files run: flutter gen-l10n - # Cache Gradle dependencies for faster subsequent builds - name: Cache Gradle dependencies uses: actions/cache@v4 with: @@ -81,6 +79,5 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - # Build the Android APK (Debug) - name: Build Android APK (Debug) run: flutter build apk -t "lib/mains/main_netknights.dart" --debug --flavor netknights diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 1f1be1cb..771b9916 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -17,22 +17,19 @@ jobs: uses: actions/setup-java@v4 with: distribution: "zulu" - java-version: "17" # Using JDK 17 for broader compatibility + java-version: "21" - name: Set up Flutter SDK uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: "3.44.0" # Use the latest stable Flutter version as of May 19, 2026 - cache: true # Enable caching for Flutter SDK itself - # cache-key and cache-path are automatically handled by subosito/flutter-action when cache: true + flutter-version: "3.44.0" + cache: true - name: Cache Flutter Pub dependencies uses: actions/cache@v4 with: - path: | - ${{ runner.tool_cache }}/flutter_plugin_cache # This is often included in ~/.pub-cache, but good to be explicit - ~/.pub-cache + path: ~/.pub-cache key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }} restore-keys: | ${{ runner.os }}-flutter- @@ -53,4 +50,4 @@ jobs: run: flutter gen-l10n - name: Run Flutter tests - run: flutter test --no-pub # Use --no-pub since pub get was already run/cached + run: flutter test --no-pub From 9312eadc8fc7b7e56f08f82966b7418c8bb7ccc6 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 19 May 2026 11:40:58 +0200 Subject: [PATCH 08/11] fix: update iOS deployment target to 15.6 and adjust connectivity_plus version constraints --- ios/Podfile | 2 +- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/Podfile b/ios/Podfile index 5e283b30..320b8f19 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -39,7 +39,7 @@ post_install do |installer| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.6' # You can remove unused permissions here # for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0' diff --git a/pubspec.lock b/pubspec.lock index 51417697..54de0388 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -317,10 +317,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "62ffa266d9a23b79fb3fcbc206afc00bb979417ba57b1324c546b5aab95ba057" + sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" url: "https://pub.dev" source: hosted - version: "7.1.1" + version: "7.0.0" connectivity_plus_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 997fba83..fc061c00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,7 +32,7 @@ dependencies: basic_utils: ^5.7.0 camera_android: ^0.10.10 collection: ^1.18.0 - connectivity_plus: ^7.0.0 + connectivity_plus: ">=7.0.0 <7.1.0" crypto: ^3.0.6 cryptography: ^2.7.0 device_info_plus: ^12.2.0 From a16913ade1ef999a70b47bf9af5e4c396ff5a6d5 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 19 May 2026 11:54:09 +0200 Subject: [PATCH 09/11] feat: add AppDimensions extension to theme in TestsAppWrapper --- test/tests_app_wrapper.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/tests_app_wrapper.dart b/test/tests_app_wrapper.dart index 8ec94d2d..d26d0f48 100644 --- a/test/tests_app_wrapper.dart +++ b/test/tests_app_wrapper.dart @@ -27,6 +27,7 @@ import 'package:privacyidea_authenticator/utils/allow_screenshot_utils.dart'; import 'package:privacyidea_authenticator/utils/app_info_utils.dart'; import 'package:privacyidea_authenticator/utils/custom_int_buffer.dart'; import 'package:privacyidea_authenticator/utils/customization/theme_extentions/action_theme.dart'; +import 'package:privacyidea_authenticator/utils/customization/theme_extentions/app_dimensions.dart'; import 'package:privacyidea_authenticator/utils/customization/theme_extentions/push_request_theme.dart'; import 'package:privacyidea_authenticator/utils/customization/theme_extentions/status_colors.dart'; import 'package:privacyidea_authenticator/utils/ecc_utils.dart'; @@ -101,6 +102,7 @@ class TestsAppWrapper extends StatelessWidget { navigatorKey: globalNavigatorKey, theme: ThemeData( extensions: [ + const AppDimensions(), StatusColors( success: const Color(0xFF4CAF50), warning: const Color(0xFFFF9800), From 7dcafae402bd63be383fb95604c8231bd206a7f7 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 19 May 2026 13:19:05 +0200 Subject: [PATCH 10/11] fix: update device_info_plus version constraints in pubspec.yaml --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 54de0388..65d62aac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -397,10 +397,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: b4fed1b2835da9d670d7bed7db79ae2a94b0f5ad6312268158a9b5479abbacdd + sha256: "4df8babf73058181227e18b08e6ea3520cf5fc5d796888d33b7cb0f33f984b7c" url: "https://pub.dev" source: hosted - version: "12.4.0" + version: "12.3.0" device_info_plus_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fc061c00..a81b889a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,7 +35,7 @@ dependencies: connectivity_plus: ">=7.0.0 <7.1.0" crypto: ^3.0.6 cryptography: ^2.7.0 - device_info_plus: ^12.2.0 + device_info_plus: ">=12.2.0 <12.4.0" easy_dynamic_theme: ^2.3.1 encrypt: ^5.0.3 expandable: ^5.0.1 From 2d34ac114b04b6460d9df048308028d7bb1e9ae4 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 19 May 2026 14:22:01 +0200 Subject: [PATCH 11/11] refactor: simplify TestsAppWrapper usage in widget tests --- test/tests_app_wrapper.dart | 2 +- .../default_refresh_indicator_test.dart | 131 ++++++------------ test/unit_test/widgets/gap_test.dart | 30 ++-- 3 files changed, 56 insertions(+), 107 deletions(-) diff --git a/test/tests_app_wrapper.dart b/test/tests_app_wrapper.dart index d26d0f48..d420fee4 100644 --- a/test/tests_app_wrapper.dart +++ b/test/tests_app_wrapper.dart @@ -137,7 +137,7 @@ class TestsAppWrapper extends StatelessWidget { GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale('en')], - home: Scaffold(body: EasyDynamicThemeWidget(child: child)), + home: Scaffold(body: child), ); }, ), diff --git a/test/unit_test/widgets/default_refresh_indicator_test.dart b/test/unit_test/widgets/default_refresh_indicator_test.dart index ed0bc73a..1ef38f94 100644 --- a/test/unit_test/widgets/default_refresh_indicator_test.dart +++ b/test/unit_test/widgets/default_refresh_indicator_test.dart @@ -18,104 +18,55 @@ * limitations under the License. */ import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/views/add_token_manually_view/add_token_manually_view_widgets/add_token_manually_row.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_container_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_state.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/widgets/default_refresh_indicator.dart'; -class LabeledDropdownButton extends StatefulWidget { - final String label; - final List values; - final bool enabled; - final List? valueLabels; - final ValueNotifier? valueNotifier; - final String postFix; - - const LabeledDropdownButton({ - required this.label, - required this.values, - required this.valueNotifier, - this.enabled = true, - this.valueLabels, - this.postFix = '', - super.key, - }); +import '../../tests_app_wrapper.dart'; +class _FakeTokenNotifier extends TokenNotifier { @override - State> createState() => - _LabeledDropdownButtonState(); + Future build({ + required firebaseUtils, + required ioClient, + required repo, + required rsaUtils, + }) async => + const TokenState(tokens: []); } -class _LabeledDropdownButtonState extends State> { - @override - void initState() { - super.initState(); - // Ensure an initial value is set - widget.valueNotifier?.value ??= widget.values.firstOrNull; - widget.valueNotifier?.addListener(_rebuild); - } - +class _FakeContainerNotifier extends TokenContainerNotifier { @override - void dispose() { - widget.valueNotifier?.removeListener(_rebuild); - super.dispose(); - } - - void _rebuild() => setState(() {}); - - @override - Widget build(BuildContext context) { - // Determine the current value, fallback to first item if current is invalid/null - final T? currentValue = - (widget.valueNotifier?.value != null && - widget.values.contains(widget.valueNotifier!.value)) - ? widget.valueNotifier!.value - : widget.values.firstOrNull; - - final Widget dropdown = AddTokenManuallyRow( - label: widget.label, - child: DropdownButton( - value: currentValue, - isExpanded: true, - onChanged: widget.enabled ? _handleChanged : null, - items: _buildMenuItems(context), - ), - ); - - if (widget.enabled) return dropdown; - - // Apply deactivation style inline - return Opacity(opacity: 0.3, child: AbsorbPointer(child: dropdown)); - } - - List> _buildMenuItems(BuildContext context) { - return widget.values.asMap().entries.map((entry) { - final index = entry.key; - final value = entry.value; - - // Determine label: Custom label -> toString() -> Postfix - String itemLabel = - (widget.valueLabels != null && index < widget.valueLabels!.length) - ? widget.valueLabels![index] - : value.toString(); - - if (widget.postFix.isNotEmpty) { - itemLabel = '$itemLabel ${widget.postFix}'; - } + Future build({ + required repo, + required containerApi, + required eccUtils, + }) async => + const TokenContainerState(containerList: []); +} - return DropdownMenuItem( - value: value, - child: Text( - itemLabel, - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.fade, - softWrap: false, +void main() { + group('DefaultRefreshIndicator Tests', () { + testWidgets('renders child widget when no push tokens or containers exist', ( + tester, + ) async { + await tester.pumpWidget( + TestsAppWrapper( + overrides: [ + tokenProvider.overrideWith(() => _FakeTokenNotifier()), + tokenContainerProvider.overrideWith(() => _FakeContainerNotifier()), + ], + child: const DefaultRefreshIndicator( + child: Text('Content'), + ), ), ); - }).toList(); - } + await tester.pump(); - void _handleChanged(T? newValue) { - if (newValue == null) return; - Logger.info('DropdownButton onChanged: $newValue'); - widget.valueNotifier?.value = newValue; - } + expect(find.text('Content'), findsOneWidget); + }); + }); } diff --git a/test/unit_test/widgets/gap_test.dart b/test/unit_test/widgets/gap_test.dart index c866db35..2a042e82 100644 --- a/test/unit_test/widgets/gap_test.dart +++ b/test/unit_test/widgets/gap_test.dart @@ -22,14 +22,18 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/utils/customization/theme_extentions/app_dimensions.dart'; import 'package:privacyidea_authenticator/widgets/gap.dart'; -import '../../tests_app_wrapper.dart'; - void main() { group('Gap Widget Tests', () { testWidgets('uses explicit size when provided', (tester) async { const double customSize = 24.0; await tester.pumpWidget( - const TestsAppWrapper(child: Gap(size: customSize)), + Theme( + data: ThemeData.light(), + child: const Directionality( + textDirection: TextDirection.ltr, + child: Gap(size: customSize), + ), + ), ); final SizedBox sizedBox = tester.widget(find.byType(SizedBox)); expect(sizedBox.width, customSize); @@ -40,23 +44,17 @@ void main() { tester, ) async { const double themeSpacing = 12.0; - await tester.pumpWidget( - TestsAppWrapper( - overrides: [], - child: Builder( - builder: (context) { - return Theme( - data: ThemeData.light().copyWith( - extensions: [AppDimensions(spacingSmall: themeSpacing)], - ), - child: const Gap(), - ); - }, + Theme( + data: ThemeData.light().copyWith( + extensions: [AppDimensions(spacingSmall: themeSpacing)], + ), + child: const Directionality( + textDirection: TextDirection.ltr, + child: Gap(), ), ), ); - final SizedBox sizedBox = tester.widget(find.byType(SizedBox)); expect(sizedBox.width, themeSpacing); expect(sizedBox.height, themeSpacing);