diff --git a/.github/workflows/flutter_build.yml b/.github/workflows/flutter_build.yml index 3a02db39f..eb15c6d3e 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.38.3" # Latest stable Flutter version as of Dec 05 2025 + 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.38.3" # Latest stable Flutter version as of Dec 05 2025 + 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 8798b4b73..771b99164 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.38.3" # Use the latest stable Flutter version as of Dec 05 2025 - 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 diff --git a/.gitignore b/.gitignore index fea212de0..2a8594237 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ .buildlog/ .history .svn/ +*.local.* # IntelliJ related *.iml @@ -30,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/app/build.gradle b/android/app/build.gradle index 37aa22aa8..04576601c 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 87e6da081..d74b5d609 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"> - 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,12 +87,15 @@ 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(), - VERSION: RequiredObjectValidator( - allowedValues: (v) => v.contains(' '), + TIME: OptionalObjectValidator(), + VERSION: OptionalObjectValidator( + transformer: (v) { + final s = v as String; + return s.contains(' ') ? s : null; + }, ), VERSION_NUMBER: Validators.stringOptional, DETAIL: OptionalObjectValidator(), @@ -102,16 +105,16 @@ 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, - version: map[VERSION] as String, + time: map[TIME] as double?, + 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 2b0125630..54f79892b 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; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart index ee3ffa5d7..4cce213df 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart @@ -50,7 +50,7 @@ final class SettingsNotifierProvider } } -String _$settingsNotifierHash() => r'153f57fa9a5b365e5af6159082baaf95e9af5d76'; +String _$settingsNotifierHash() => r'2cb678680513b175f42900cebb9d12bacd88853b'; final class SettingsNotifierFamily extends $Family with diff --git a/lib/views/container_view/container_widgets/container_actions/transfer_container_action.dart b/lib/views/container_view/container_widgets/container_actions/transfer_container_action.dart index b9f0138f0..e3c1d6a5d 100644 --- a/lib/views/container_view/container_widgets/container_actions/transfer_container_action.dart +++ b/lib/views/container_view/container_widgets/container_actions/transfer_container_action.dart @@ -20,7 +20,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/token_container.dart'; @@ -56,7 +55,7 @@ class TransferContainerAction extends ConsumerSlideableAction { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(MdiIcons.transfer), + Icon(Icons.compare_arrows), Text( AppLocalizations.of(context)!.transferButton, overflow: TextOverflow.fade, diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header_icon.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header_icon.dart index db863414e..e4d4c3ff8 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header_icon.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header_icon.dart @@ -20,7 +20,6 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import '../../../../../utils/customization/theme_extentions/action_theme.dart'; import '../../../../../widgets/custom_trailing.dart'; @@ -85,8 +84,8 @@ class TokenFolderExpandableHeaderIcon extends StatelessWidget { : Matrix4.identity(), child: Icon( isExpanded - ? MdiIcons.lockOpenVariant - : MdiIcons.lock, + ? Icons.lock_open + : Icons.lock, color: Theme.of( context, ).extension()?.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 06764aaf9..d853c8c77 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 c42073347..65d62aac4 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: @@ -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: @@ -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: @@ -1152,10 +1144,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: @@ -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 @@ -1685,26 +1669,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: diff --git a/pubspec.yaml b/pubspec.yaml index 99c329c9f..a81b889a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,10 +32,10 @@ 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 + device_info_plus: ">=12.2.0 <12.4.0" easy_dynamic_theme: ^2.3.1 encrypt: ^5.0.3 expandable: ^5.0.1 @@ -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 diff --git a/test/tests_app_wrapper.dart b/test/tests_app_wrapper.dart index 8ec94d2d4..d420fee4b 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), @@ -135,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 ed0bc73a9..1ef38f942 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 c866db354..2a042e823 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);