feat(mobile): enabled DCM (#17957)

* enable DCM in CI

* chore: up version

* chore: up version

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Andreas Tollkötter 2025-06-09 18:09:02 +02:00 committed by GitHub
parent 16f83c0aa9
commit b890440f6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 257 additions and 165 deletions

View File

@ -58,6 +58,14 @@ jobs:
run: dart pub get
working-directory: ./mobile
- name: Install DCM
run: |
sudo apt-get update
wget -qO- https://dcm.dev/pgp-key.public | sudo gpg --dearmor -o /usr/share/keyrings/dcm.gpg
echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list
sudo apt-get update
sudo apt-get install dcm
- name: Generate translation file
run: make translation
working-directory: ./mobile
@ -100,6 +108,10 @@ jobs:
run: dart run custom_lint
working-directory: ./mobile
- name: Run DCM
run: dcm analyze lib
working-directory: ./mobile
zizmor:
name: zizmor
runs-on: ubuntu-latest

View File

@ -17,6 +17,27 @@ To add a new translation text, enter the key-value pair in the `i18n/en.json` in
make translation
```
## Static Analysis
The following checks of static analysis must pass for a contribution to the mobile app to be valid:
```bash
dart format lib
dart analyze
dart run custom_lint
dcm analyze lib
```
[DCM](https://dcm.dev/) is a vendor tool that needs to be downloaded manually to run locally.
Immich was provided an open source license.
To use it, it is important that you do not have an active free tier license (can be verified with `dcm license`).
If you have write-access to the Immich repository directly, running dcm in your clone should just work.
If you are working on a clone of a fork, you need to connect to the main Immich repository as remote first:
```bash
git remote add immich git@github.com:immich-app/immich.git
```
## Immich-Flutter Directory Structure
Below are the directory inside the `lib` directory:

View File

@ -128,82 +128,169 @@ custom_lint:
- test/**.dart
dart_code_metrics:
extends:
- recommended
rules:
# Common
- arguments-ordering:
last:
- child
- children
- avoid-accessing-other-classes-private-members
- avoid-assigning-to-static-field
- avoid-assignments-as-conditions
- avoid-async-call-in-sync-function
- avoid-collapsible-if
- avoid-collection-equality-checks
- avoid-complex-loop-conditions
- avoid-declaring-call-method
- avoid-extensions-on-records
- avoid-function-type-in-records
- avoid-future-ignore
- avoid-global-state
- avoid-inverted-boolean-checks
- avoid-late-final-reassignment
- avoid-local-functions:
exclude:
- test/**.dart
- avoid-negated-conditions
- avoid-nested-streams-and-futures
- avoid-referencing-subclasses
- avoid-unnecessary-continue
- avoid-unnecessary-nullable-return-type: false
- binary-expression-operand-order
- pattern-fields-ordering
- prefer-abstract-final-static-class
- prefer-commenting-future-delayed
- prefer-early-return
- prefer-first
- prefer-immediate-return
- prefer-last
- prefer-simpler-boolean-expressions
- prefer-switch-expression
- prefer-type-over-var
- use-existing-destructuring
- use-existing-variable
# Flutter
- avoid-border-all
- avoid-complex-arithmetic-expressions
- avoid-expanded-as-spacer
- avoid-if-with-many-branches
- avoid-inherited-widget-in-initstate
- avoid-late-context
- avoid-returning-widgets
- avoid-shrink-wrap-in-lists
- avoid-single-child-column-or-row
- avoid-stateless-widget-initialized-fields
- avoid-wrapping-in-padding
- prefer-align-over-container
- prefer-const-border-radius
- prefer-correct-callback-field-name: false
- prefer-correct-edge-insets-constructor
- prefer-define-hero-tag
- prefer-extracting-callbacks
- prefer-for-loop-in-children
- prefer-match-file-name: false
- prefer-sliver-prefix
- prefer-spacing
- prefer-text-rich
- prefer-transform-over-container
- prefer-using-list-view
- prefer-widget-private-members:
ignore-static: true
- use-closest-build-context
# riverpod
- avoid-calling-notifier-members-inside-build
- avoid-notifier-constructors
- avoid-ref-read-inside-build
- avoid-ref-watch-outside-build
- avoid-unnecessary-consumer-widgets
- dispose-provided-instances
- use-ref-read-synchronously
# All rules from "recommended" preset
# Show potential errors
# - avoid-cascade-after-if-null
# - avoid-collection-methods-with-unrelated-types
# - avoid-duplicate-exports
# - avoid-dynamic
# - avoid-missing-enum-constant-in-map
# - avoid-passing-async-when-sync-expected
# - avoid-throw-in-catch-block
- avoid-unused-parameters
# - avoid-unnecessary-type-assertions
# - avoid-unnecessary-type-casts
# - avoid-unrelated-type-assertions
# - avoid-unrelated-type-casts
# - no-empty-block
# - no-equal-then-else
# - prefer-correct-test-file-name
# - prefer-match-file-name
# - prefer-return-await
# - avoid-self-assignment
# - avoid-self-compare
# - avoid-shadowing
# - prefer-iterable-of
# - no-equal-switch-case
# - no-equal-conditions
# - avoid-equal-expressions
# - avoid-missed-calls
# - avoid-unnecessary-negations
# - avoid-unused-generics
# - function-always-returns-null
# - avoid-throw-objects-without-tostring
# - avoid-unsafe-collection-methods
# - prefer-wildcard-pattern
# - no-equal-switch-expression-cases
# - avoid-future-tostring
# - avoid-unassigned-late-fields
# - avoid-nested-futures
# - avoid-generics-shadowing
# - prefer-parentheses-with-if-null
# - no-equal-nested-conditions
# - avoid-shadowed-extension-methods
# - avoid-unnecessary-conditionals
# - avoid-double-slash-imports
# - avoid-map-keys-contains
# - prefer-correct-json-casts
# - avoid-duplicate-mixins
# - avoid-nullable-interpolation
# - avoid-unused-instances
# - prefer-correct-for-loop-increment
# - prefer-public-exception-classes
# - avoid-uncaught-future-errors
# - always-remove-listener
# - avoid-unnecessary-setstate
# - check-for-equals-in-render-object-setters
# - consistent-update-render-object
# - use-setstate-synchronously
# - avoid-incomplete-copy-with
# - proper-super-calls
# - dispose-fields
# - avoid-empty-setstate
# - avoid-state-constructors
# - avoid-recursive-widget-calls
# - avoid-missing-image-alt
# - avoid-passing-self-as-argument
# - avoid-unnecessary-if
# - avoid-unconditional-break
# - avoid-referencing-discarded-variables
# - avoid-unnecessary-local-late
# - avoid-wildcard-cases-with-enums
# - match-getter-setter-field-names
# - avoid-accessing-collections-by-constant-index
# - prefer-unique-test-names
# - avoid-duplicate-cascades
# - prefer-specific-cases-first
# - avoid-duplicate-switch-case-conditions
# - prefer-explicit-function-type
# - avoid-misused-test-matchers
# - avoid-duplicate-test-assertions
# - prefer-switch-with-enums
# - prefer-any-or-every
# - avoid-duplicate-map-keys
# - avoid-nullable-tostring
# - avoid-undisposed-instances
# - avoid-duplicate-initializers
# - avoid-unassigned-stream-subscriptions
# - avoid-empty-test-groups
# - avoid-not-encodable-in-to-json
# - avoid-contradictory-expressions
# - avoid-excessive-expressions
# - prefer-private-extension-type-field
# - avoid-renaming-representation-getters
# - avoid-empty-spread
# - avoid-unnecessary-gesture-detector
# - avoid-missing-completer-stack-trace
# - avoid-casting-to-extension-type
# - prefer-overriding-parent-equality
# - avoid-missing-controller
# - avoid-unknown-pragma
# - avoid-conditions-with-boolean-literals
# - avoid-multi-assignment
# - avoid-collection-equality-checks
# - avoid-only-rethrow
# - avoid-incorrect-image-opacity
# - avoid-misused-set-literals
# - dispose-class-fields
# - avoid-suspicious-super-overrides
# - avoid-assignments-as-conditions
# - avoid-unused-assignment
# - avoid-unnecessary-overrides
# - avoid-implicitly-nullable-extension-types
# Enable with the next release
# - avoid-late-final-reassignment
# - avoid-duplicate-constant-values
# - function-always-returns-same-value
# - avoid-flexible-outside-flex
# - avoid-unnecessary-patterns
# - use-closest-build-context
# - avoid-commented-out-code
# - avoid-recursive-tostring
# - avoid-enum-values-by-index
# - avoid-constant-assert-conditions
# - avoid-inconsistent-digit-separators
# - pass-existing-future-to-future-builder
# - pass-existing-stream-to-stream-builder
# Code simplification
# - avoid-redundant-async
# - avoid-redundant-else
# - avoid-unnecessary-nullable-return-type
# - avoid-redundant-pragma-inline
# - avoid-nested-records
# - avoid-redundant-positional-field-name
# - avoid-explicit-pattern-field-name
# - prefer-simpler-patterns-null-check
# - avoid-unnecessary-return
# - avoid-duplicate-patterns
# - avoid-keywords-in-wildcard-pattern
# - avoid-unnecessary-futures
# - avoid-unnecessary-reassignment
# - avoid-unnecessary-call
# - avoid-unnecessary-stateful-widgets
# - prefer-dedicated-media-query-methods
# - avoid-unnecessary-overrides-in-state
# - move-variable-closer-to-its-usage
# - avoid-nullable-parameters-with-default-values
# - prefer-null-aware-spread
# - avoid-inferrable-type-arguments
# - avoid-unnecessary-super
# - avoid-unnecessary-collections
# - avoid-unnecessary-extends
# - avoid-unnecessary-enum-arguments
# - prefer-contains
# Enable with the next release
# - prefer-simpler-boolean-expressions
# - prefer-spacing
# - avoid-unnecessary-continue
# - avoid-unnecessary-compare-to
# Style
# - prefer-trailing-comma
# - unnecessary-trailing-comma
# - prefer-declaring-const-constructor
# - prefer-single-widget-per-file
# - prefer-prefixed-global-constants
# - prefer-correct-callback-field-name

1
mobile/dcm_global.yaml Normal file
View File

@ -0,0 +1 @@
version: '>=1.29.0 <1.30.0'

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
List<ColorFilter> filters = [
const List<ColorFilter> filters = [
//Original
const ColorFilter.matrix([
ColorFilter.matrix([
1,
0,
0,
@ -25,7 +25,7 @@ List<ColorFilter> filters = [
0,
]),
//Vintage
const ColorFilter.matrix([
ColorFilter.matrix([
0.8,
0.1,
0.1,
@ -48,7 +48,7 @@ List<ColorFilter> filters = [
0,
]),
//Mood
const ColorFilter.matrix([
ColorFilter.matrix([
1.2,
0.1,
0.1,
@ -71,7 +71,7 @@ List<ColorFilter> filters = [
0,
]),
//Crisp
const ColorFilter.matrix([
ColorFilter.matrix([
1.2,
0,
0,
@ -94,7 +94,7 @@ List<ColorFilter> filters = [
0,
]),
//Cool
const ColorFilter.matrix([
ColorFilter.matrix([
0.9,
0,
0.2,
@ -117,7 +117,7 @@ List<ColorFilter> filters = [
0,
]),
//Blush
const ColorFilter.matrix([
ColorFilter.matrix([
1.1,
0.1,
0.1,
@ -140,7 +140,7 @@ List<ColorFilter> filters = [
0,
]),
//Sunkissed
const ColorFilter.matrix([
ColorFilter.matrix([
1.3,
0,
0.1,
@ -163,7 +163,7 @@ List<ColorFilter> filters = [
0,
]),
//Fresh
const ColorFilter.matrix([
ColorFilter.matrix([
1.2,
0,
0,
@ -186,7 +186,7 @@ List<ColorFilter> filters = [
0,
]),
//Classic
const ColorFilter.matrix([
ColorFilter.matrix([
1.1,
0,
-0.1,
@ -209,7 +209,7 @@ List<ColorFilter> filters = [
0,
]),
//Lomo-ish
const ColorFilter.matrix([
ColorFilter.matrix([
1.5,
0,
0.1,
@ -232,7 +232,7 @@ List<ColorFilter> filters = [
0,
]),
//Nashville
const ColorFilter.matrix([
ColorFilter.matrix([
1.2,
0.15,
-0.15,
@ -255,7 +255,7 @@ List<ColorFilter> filters = [
0,
]),
//Valencia
const ColorFilter.matrix([
ColorFilter.matrix([
1.15,
0.1,
0.1,
@ -278,7 +278,7 @@ List<ColorFilter> filters = [
0,
]),
//Clarendon
const ColorFilter.matrix([
ColorFilter.matrix([
1.2,
0,
0,
@ -301,7 +301,7 @@ List<ColorFilter> filters = [
0,
]),
//Moon
const ColorFilter.matrix([
ColorFilter.matrix([
0.33,
0.33,
0.33,
@ -324,7 +324,7 @@ List<ColorFilter> filters = [
0,
]),
//Willow
const ColorFilter.matrix([
ColorFilter.matrix([
0.5,
0.5,
0.5,
@ -347,7 +347,7 @@ List<ColorFilter> filters = [
0,
]),
//Kodak
const ColorFilter.matrix([
ColorFilter.matrix([
1.3,
0.1,
-0.1,
@ -370,7 +370,7 @@ List<ColorFilter> filters = [
0,
]),
//Frost
const ColorFilter.matrix([
ColorFilter.matrix([
0.8,
0.2,
0.1,
@ -393,7 +393,7 @@ List<ColorFilter> filters = [
0,
]),
//Night Vision
const ColorFilter.matrix([
ColorFilter.matrix([
0.1,
0.95,
0.2,
@ -416,7 +416,7 @@ List<ColorFilter> filters = [
0,
]),
//Sunset
const ColorFilter.matrix([
ColorFilter.matrix([
1.5,
0.2,
0,
@ -439,7 +439,7 @@ List<ColorFilter> filters = [
0,
]),
//Noir
const ColorFilter.matrix([
ColorFilter.matrix([
1.3,
-0.3,
0.1,
@ -462,7 +462,7 @@ List<ColorFilter> filters = [
0,
]),
//Dreamy
const ColorFilter.matrix([
ColorFilter.matrix([
1.1,
0.1,
0.1,
@ -485,7 +485,7 @@ List<ColorFilter> filters = [
0,
]),
//Sepia
const ColorFilter.matrix([
ColorFilter.matrix([
0.393,
0.769,
0.189,
@ -508,7 +508,7 @@ List<ColorFilter> filters = [
0,
]),
//Radium
const ColorFilter.matrix([
ColorFilter.matrix([
1.438,
-0.062,
-0.062,
@ -531,7 +531,7 @@ List<ColorFilter> filters = [
0,
]),
//Aqua
const ColorFilter.matrix([
ColorFilter.matrix([
0.2126,
0.7152,
0.0722,
@ -554,7 +554,7 @@ List<ColorFilter> filters = [
0,
]),
//Purple Haze
const ColorFilter.matrix([
ColorFilter.matrix([
1.3,
0,
1.2,
@ -577,7 +577,7 @@ List<ColorFilter> filters = [
0,
]),
//Lemonade
const ColorFilter.matrix([
ColorFilter.matrix([
1.2,
0.1,
0,
@ -600,7 +600,7 @@ List<ColorFilter> filters = [
0,
]),
//Caramel
const ColorFilter.matrix([
ColorFilter.matrix([
1.6,
0.2,
0,
@ -623,7 +623,7 @@ List<ColorFilter> filters = [
0,
]),
//Peachy
const ColorFilter.matrix([
ColorFilter.matrix([
1.3,
0.5,
0,
@ -646,7 +646,7 @@ List<ColorFilter> filters = [
0,
]),
//Neon
const ColorFilter.matrix([
ColorFilter.matrix([
1,
0,
1,
@ -669,7 +669,7 @@ List<ColorFilter> filters = [
0,
]),
//Cold Morning
const ColorFilter.matrix([
ColorFilter.matrix([
0.9,
0.1,
0.2,
@ -692,7 +692,7 @@ List<ColorFilter> filters = [
0,
]),
//Lush
const ColorFilter.matrix([
ColorFilter.matrix([
0.9,
0.2,
0,
@ -715,7 +715,7 @@ List<ColorFilter> filters = [
0,
]),
//Urban Neon
const ColorFilter.matrix([
ColorFilter.matrix([
1.1,
0,
0.3,
@ -738,7 +738,7 @@ List<ColorFilter> filters = [
0,
]),
//Monochrome
const ColorFilter.matrix([
ColorFilter.matrix([
0.6,
0.2,
0.2,

View File

@ -229,7 +229,6 @@ class ImmichAppState extends ConsumerState<ImmichApp>
}
}
// ignore: prefer-single-widget-per-file
class MainWidget extends StatelessWidget {
const MainWidget({super.key});

View File

@ -1,5 +1,3 @@
import 'dart:typed_data';
import 'package:immich_mobile/entities/album.entity.dart';
class AvailableAlbum {
@ -16,7 +14,6 @@ class AvailableAlbum {
Album? album,
int? assetCount,
DateTime? lastBackup,
Uint8List? thumbnailData,
}) {
return AvailableAlbum(
album: album ?? this.album,

View File

@ -1,5 +1,3 @@
// ignore_for_file: add-copy-with
sealed class MapEvent {
const MapEvent();
}

View File

@ -236,7 +236,7 @@ class GalleryViewerPage extends HookConsumerWidget {
});
});
PhotoViewGalleryPageOptions buildImage(BuildContext context, Asset asset) {
PhotoViewGalleryPageOptions buildImage(Asset asset) {
return PhotoViewGalleryPageOptions(
onDragStart: (_, details, __) {
localPosition.value = details.localPosition;
@ -312,7 +312,7 @@ class GalleryViewerPage extends HookConsumerWidget {
}
if (newAsset.isImage && !isPlayingMotionVideo) {
return buildImage(context, newAsset);
return buildImage(newAsset);
}
return buildVideo(context, newAsset);
}

View File

@ -1,5 +1,3 @@
// ignore_for_file: avoid-local-functions
import 'dart:async';
import 'package:auto_route/auto_route.dart';
@ -144,7 +142,6 @@ class _Feature {
final Future<void> Function(BuildContext, WidgetRef _) onTap;
}
// ignore: prefer-single-widget-per-file
class _DevLogs extends StatelessWidget {
const _DevLogs();
@ -172,7 +169,6 @@ class _DevLogs extends StatelessWidget {
builder: (_, logMessages) {
return ListView.separated(
itemBuilder: (ctx, index) {
// ignore: avoid-unsafe-collection-methods
final logMessage = logMessages.data![index];
return ListTile(
title: Text(

View File

@ -1,5 +1,3 @@
// ignore_for_file: prefer-single-widget-per-file
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';

View File

@ -5,4 +5,4 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'api.provider.g.dart';
@Riverpod(keepAlive: true)
ApiService apiService(Ref ref) => ApiService();
ApiService apiService(Ref _) => ApiService();

Binary file not shown.

View File

@ -5,4 +5,4 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'app_settings.provider.g.dart';
@Riverpod(keepAlive: true)
AppSettingsService appSettingsService(Ref ref) => AppSettingsService();
AppSettingsService appSettingsService(Ref _) => AppSettingsService();

Binary file not shown.

View File

@ -39,6 +39,6 @@ final assetStackStateProvider = StateNotifierProvider.autoDispose
);
@riverpod
int assetStackIndex(Ref ref, Asset asset) {
int assetStackIndex(Ref _) {
return -1;
}

View File

@ -144,7 +144,7 @@ class DownloadStateNotifier extends StateNotifier<DownloadState> {
return await _downloadService.downloadAll(assets);
}
void downloadAsset(Asset asset, BuildContext context) async {
void downloadAsset(Asset asset) async {
await _downloadService.download(asset);
}

View File

@ -7,7 +7,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'immich_logo_provider.g.dart';
@riverpod
Future<Uint8List> immichLogo(Ref ref) async {
Future<Uint8List> immichLogo(Ref _) async {
final json = await rootBundle.loadString('assets/immich-logo.json');
final j = jsonDecode(json);
return base64Decode(j['content']);

Binary file not shown.

View File

@ -18,7 +18,6 @@ import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
import 'package:immich_mobile/models/backup/success_upload_asset.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart';
@ -489,7 +488,6 @@ class BackgroundService {
_cancellationToken!,
pmProgressHandler: pmProgressHandler,
onSuccess: (result) => _onAssetUploaded(
result: result,
shouldNotify: notifyTotalProgress,
),
onProgress: (bytes, totalBytes) =>
@ -511,7 +509,6 @@ class BackgroundService {
}
void _onAssetUploaded({
required SuccessUploadAsset result,
bool shouldNotify = false,
}) async {
if (!shouldNotify) {

View File

@ -184,10 +184,10 @@ class BackupVerificationService {
// for images: make sure they are pixel-wise identical
// (skip first few KBs containing metadata)
final Uint64List localImage =
_fakeDecodeImg(local, await file.readAsBytes());
_fakeDecodeImg(await file.readAsBytes());
final res = await apiService.assetsApi
.downloadAssetWithHttpInfo(remote.remoteId!);
final Uint64List remoteImage = _fakeDecodeImg(remote, res.bodyBytes);
final Uint64List remoteImage = _fakeDecodeImg(res.bodyBytes);
final eq = const ListEquality().equals(remoteImage, localImage);
return eq;
@ -198,7 +198,7 @@ class BackupVerificationService {
return false;
}
static Uint64List _fakeDecodeImg(Asset asset, Uint8List bytes) {
static Uint64List _fakeDecodeImg(Uint8List bytes) {
const headerLength = 131072; // assume header is at most 128 KB
final start = bytes.length < headerLength * 2
? (bytes.length ~/ (4 * 8)) * 8

View File

@ -1,5 +1,3 @@
// ignore_for_file: avoid-unsafe-collection-methods
import 'dart:convert';
import 'dart:io';

View File

@ -1,5 +1,3 @@
// ignore_for_file: avoid-unsafe-collection-methods
import 'dart:async';
import 'dart:convert';
import 'dart:io';

View File

@ -94,7 +94,7 @@ Future<void> handleFavoriteAssets(
ImmichToast.show(
context: context,
msg: toastMessage,
gravity: ToastGravity.BOTTOM,
gravity: toastGravity,
);
}
}
@ -164,9 +164,8 @@ Future<void> handleSetAssetsVisibility(
WidgetRef ref,
BuildContext context,
AssetVisibilityEnum visibility,
List<Asset> selection, {
ToastGravity toastGravity = ToastGravity.BOTTOM,
}) async {
List<Asset> selection,
) async {
if (selection.isNotEmpty) {
await ref
.watch(assetProvider.notifier)

View File

@ -1,4 +1,3 @@
// ignore_for_file: library_private_types_in_public_api
// Based on https://stackoverflow.com/a/52625182
import 'dart:async';
@ -164,7 +163,6 @@ class _CustomLongPressGestureRecognizer extends LongPressGestureRecognizer {
}
}
// ignore: prefer-single-widget-per-file
class AssetIndexWrapper extends SingleChildRenderObjectWidget {
final int rowIndex;
final int sectionIndex;
@ -177,6 +175,7 @@ class AssetIndexWrapper extends SingleChildRenderObjectWidget {
});
@override
// ignore: library_private_types_in_public_api
_AssetIndexProxy createRenderObject(BuildContext context) {
return _AssetIndexProxy(
index: AssetIndex(rowIndex: rowIndex, sectionIndex: sectionIndex),
@ -186,6 +185,7 @@ class AssetIndexWrapper extends SingleChildRenderObjectWidget {
@override
void updateRenderObject(
BuildContext context,
// ignore: library_private_types_in_public_api
_AssetIndexProxy renderObject,
) {
renderObject.index =

View File

@ -1,5 +1,3 @@
// ignore_for_file: prefer-single-widget-per-file
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';

View File

@ -80,8 +80,7 @@ class DraggableScrollbar extends StatefulWidget {
this.labelTextBuilder,
this.labelConstraints,
}) : assert(child.scrollDirection == Axis.vertical),
scrollThumbBuilder =
_thumbRRectBuilder(scrollThumbKey, alwaysVisibleScrollThumb);
scrollThumbBuilder = _thumbRRectBuilder(alwaysVisibleScrollThumb);
DraggableScrollbar.arrows({
super.key,
@ -97,8 +96,7 @@ class DraggableScrollbar extends StatefulWidget {
this.labelTextBuilder,
this.labelConstraints,
}) : assert(child.scrollDirection == Axis.vertical),
scrollThumbBuilder =
_thumbArrowBuilder(scrollThumbKey, alwaysVisibleScrollThumb);
scrollThumbBuilder = _thumbArrowBuilder(alwaysVisibleScrollThumb);
DraggableScrollbar.semicircle({
super.key,
@ -201,7 +199,6 @@ class DraggableScrollbar extends StatefulWidget {
}
static ScrollThumbBuilder _thumbArrowBuilder(
Key? scrollThumbKey,
bool alwaysVisibleScrollThumb,
) {
return (
@ -239,7 +236,6 @@ class DraggableScrollbar extends StatefulWidget {
}
static ScrollThumbBuilder _thumbRRectBuilder(
Key? scrollThumbKey,
bool alwaysVisibleScrollThumb,
) {
return (

View File

@ -48,7 +48,7 @@ class ThumbnailImage extends ConsumerWidget {
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = asset.id == noDbId;
Widget buildSelectionIcon(Asset asset) {
Widget buildSelectionIcon() {
if (isSelected) {
return Container(
decoration: BoxDecoration(
@ -233,7 +233,7 @@ class ThumbnailImage extends ConsumerWidget {
padding: const EdgeInsets.all(3.0),
child: Align(
alignment: Alignment.topLeft,
child: buildSelectionIcon(asset),
child: buildSelectionIcon(),
),
),
],

View File

@ -235,7 +235,6 @@ class BottomGalleryBar extends ConsumerWidget {
ref.read(downloadStateProvider.notifier).downloadAsset(
asset,
context,
);
}

View File

@ -96,7 +96,7 @@ class GalleryAppBar extends ConsumerWidget {
}
handleDownloadAsset() {
ref.read(downloadStateProvider.notifier).downloadAsset(asset, context);
ref.read(downloadStateProvider.notifier).downloadAsset(asset);
}
handleLocateAsset() async {

View File

@ -13,7 +13,8 @@ OctoSet blurHashOrPlaceholder(
}) {
return OctoSet(
placeholderBuilder: blurHashPlaceholderBuilder(blurhash, fit: fit),
errorBuilder: blurHashErrorBuilder(blurhash, fit: fit),
errorBuilder:
blurHashErrorBuilder(blurhash, fit: fit, message: errorMessage),
);
}

View File

@ -28,7 +28,6 @@ mixin HitCornersDetector on PhotoViewControllerDelegate {
bool _shouldMoveAxis(
HitCorners hitCorners,
double mainAxisMove,
double crossAxisMove,
) {
if (mainAxisMove == 0) {
return false;
@ -47,17 +46,15 @@ mixin HitCornersDetector on PhotoViewControllerDelegate {
bool _shouldMoveX(Offset move) {
final hitCornersX = _hitCornersX();
final mainAxisMove = move.dx;
final crossAxisMove = move.dy;
return _shouldMoveAxis(hitCornersX, mainAxisMove, crossAxisMove);
return _shouldMoveAxis(hitCornersX, mainAxisMove);
}
bool _shouldMoveY(Offset move) {
final hitCornersY = _hitCornersY();
final mainAxisMove = move.dy;
final crossAxisMove = move.dx;
return _shouldMoveAxis(hitCornersY, mainAxisMove, crossAxisMove);
return _shouldMoveAxis(hitCornersY, mainAxisMove);
}
bool shouldMove(Offset move, Axis mainAxis) {