[mob][photos] Album UI improvements (#6176)
## Description ### Changes 1. Album corner radius (114 size - 12px corner radius) (100 size - 8px corner radius) 2. Thumbnail view size 60 - corner radius 4 3. Padding between album thumbnail and Album name is 6 4. Padding between Album name and number of photos is 2 5. Album name (text size) - Small regular , Full black (color) 6. Number of photos (text size) - Mini Regular , Text muted (color) 7. New design for Add album thumbnail 8. Distance between album vertically & horizontally 8px 9. List view border radius 6px and thumbnail radius 4px
This commit is contained in:
4
mobile/assets/icons/list_view_icon_dark.svg
Normal file
4
mobile/assets/icons/list_view_icon_dark.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.75 6.75H21.75M6.75 12H21.75M6.75 17.25H21.75" stroke="white" stroke-opacity="0.6" stroke-width="2.25" stroke-linejoin="round"/>
|
||||
<path d="M3 6H4.5V7.5H3V6ZM3 11.25H4.5V12.75H3V11.25ZM3 16.5H4.5V18H3V16.5Z" stroke="white" stroke-opacity="0.6" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 425 B |
4
mobile/assets/icons/list_view_icon_light.svg
Normal file
4
mobile/assets/icons/list_view_icon_light.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.75 6.75H21.75M6.75 12H21.75M6.75 17.25H21.75" stroke="black" stroke-opacity="0.6" stroke-width="2.25" stroke-linejoin="round"/>
|
||||
<path d="M3 6H4.5V7.5H3V6ZM3 11.25H4.5V12.75H3V11.25ZM3 16.5H4.5V18H3V16.5Z" stroke="black" stroke-opacity="0.6" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 425 B |
3
mobile/assets/icons/search_icon_dark.svg
Normal file
3
mobile/assets/icons/search_icon_dark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.7548 14.394L12.1277 10.7669C13.0009 9.60436 13.4723 8.18933 13.4707 6.73536C13.4707 3.02151 10.4492 0 6.73536 0C3.02151 0 0 3.02151 0 6.73536C0 10.4492 3.02151 13.4707 6.73536 13.4707C8.18933 13.4723 9.60436 13.0009 10.7669 12.1277L14.394 15.7548C14.5776 15.9189 14.8171 16.0065 15.0632 15.9996C15.3094 15.9927 15.5436 15.8919 15.7177 15.7177C15.8919 15.5436 15.9927 15.3094 15.9996 15.0632C16.0065 14.8171 15.9189 14.5776 15.7548 14.394ZM1.92439 6.73536C1.92439 5.78384 2.20655 4.85369 2.73518 4.06253C3.26382 3.27137 4.01519 2.65473 4.89428 2.2906C5.77337 1.92647 6.7407 1.8312 7.67393 2.01683C8.60717 2.20246 9.4644 2.66066 10.1372 3.33349C10.8101 4.00632 11.2683 4.86355 11.4539 5.79679C11.6395 6.73002 11.5442 7.69735 11.1801 8.57644C10.816 9.45553 10.1994 10.2069 9.40819 10.7355C8.61703 11.2642 7.68688 11.5463 6.73536 11.5463C5.45988 11.5448 4.23708 11.0374 3.33518 10.1355C2.43328 9.23364 1.92592 8.01084 1.92439 6.73536Z" fill="white" fill-opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
mobile/assets/icons/search_icon_light.svg
Normal file
3
mobile/assets/icons/search_icon_light.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.7548 14.394L12.1277 10.7669C13.0009 9.60436 13.4723 8.18933 13.4707 6.73536C13.4707 3.02151 10.4492 0 6.73536 0C3.02151 0 0 3.02151 0 6.73536C0 10.4492 3.02151 13.4707 6.73536 13.4707C8.18933 13.4723 9.60436 13.0009 10.7669 12.1277L14.394 15.7548C14.5776 15.9189 14.8171 16.0065 15.0632 15.9996C15.3094 15.9927 15.5436 15.8919 15.7177 15.7177C15.8919 15.5436 15.9927 15.3094 15.9996 15.0632C16.0065 14.8171 15.9189 14.5776 15.7548 14.394ZM1.92439 6.73536C1.92439 5.78384 2.20655 4.85369 2.73518 4.06253C3.26382 3.27137 4.01519 2.65473 4.89428 2.2906C5.77337 1.92647 6.7407 1.8312 7.67393 2.01683C8.60717 2.20246 9.4644 2.66066 10.1372 3.33349C10.8101 4.00632 11.2683 4.86355 11.4539 5.79679C11.6395 6.73002 11.5442 7.69735 11.1801 8.57644C10.816 9.45553 10.1994 10.2069 9.40819 10.7355C8.61703 11.2642 7.68688 11.5463 6.73536 11.5463C5.45988 11.5448 4.23708 11.0374 3.33518 10.1355C2.43328 9.23364 1.92592 8.01084 1.92439 6.73536Z" fill="black" fill-opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -463,7 +463,7 @@ SPEC CHECKSUMS:
|
||||
flutter_native_splash: 6cad9122ea0fad137d23137dd14b937f3e90b145
|
||||
flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
|
||||
flutter_sodium: 7e4621538491834eba53bd524547854bcbbd6987
|
||||
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
||||
flutter_timezone: ac3da59ac941ff1c98a2e1f0293420e020120282
|
||||
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
|
||||
8
mobile/lib/events/create_new_album_event.dart
Normal file
8
mobile/lib/events/create_new_album_event.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import "package:photos/events/event.dart";
|
||||
import "package:photos/models/collection/collection.dart";
|
||||
|
||||
class CreateNewAlbumEvent extends Event {
|
||||
final Collection collection;
|
||||
|
||||
CreateNewAlbumEvent(this.collection);
|
||||
}
|
||||
@@ -1690,5 +1690,14 @@
|
||||
"onTheRoad": "På veien igjen",
|
||||
"food": "Kulinær glede",
|
||||
"pets": "Pelsvenner",
|
||||
"wishThemAHappyBirthday": "Wish ${name} a happy birthday! 🎉"
|
||||
"cLIcon": "Nytt ikon",
|
||||
"cLIconDesc": "Endelig er et nytt appikon, som vi tror best representerer arbeidet vårt. Vi har også lagt til en icon-switcher slik at du kan fortsette å bruke det gamle ikonet.",
|
||||
"cLMemories": "Minner",
|
||||
"cLMemoriesDesc": "Gjenoppdag dine spesielle øyeblikk - fremhev dine favorittpersoner, dine turer og ferier, de beste bildene dine, og mye mer. Skru på maskinlæring, merk deg selv og navngi vennene dine for best mulig opplevelse.",
|
||||
"cLWidgets": "Widgeter",
|
||||
"cLWidgetsDesc": "Hjemmeskjermwidgeter som er integrert med minner er nå tilgjengelige. De vil vise dine spesielle øyeblikk uten å åpne appen.",
|
||||
"cLFamilyPlan": "Begrensninger for familieabonnement",
|
||||
"cLFamilyPlanDesc": "Du kan nå sette grenser for hvor mye lagringsplass familiemedlemmer kan bruke.",
|
||||
"cLBulkEdit": "Masseendring av datoer",
|
||||
"cLBulkEditDesc": "Du kan nå velge flere bilder, og redigere dato/klokkeslett for alle med en rask handling. Forskyving av datoer støttes også."
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import "package:photos/ui/components/buttons/icon_button_widget.dart";
|
||||
import 'package:photos/ui/viewer/file/no_thumbnail_widget.dart';
|
||||
import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
|
||||
|
||||
///https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design?node-id=7480%3A33462&t=H5AvR79OYDnB9ekw-4
|
||||
///https://www.figma.com/design/SYtMyLBs5SAOkTbfMMzhqt/Ente-Visual-Design?node-id=39181-172145&t=3qmSZWpXF3ZC4JGN-1
|
||||
class AlbumColumnItemWidget extends StatelessWidget {
|
||||
final Collection collection;
|
||||
final List<Collection> selectedCollections;
|
||||
@@ -36,7 +36,7 @@ class AlbumColumnItemWidget extends StatelessWidget {
|
||||
isSelected ? colorScheme.strokeMuted : colorScheme.strokeFainter,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(4),
|
||||
Radius.circular(6),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
@@ -47,9 +47,7 @@ class AlbumColumnItemWidget extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.horizontal(
|
||||
left: Radius.circular(4),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: SizedBox(
|
||||
height: sideOfThumbnail,
|
||||
width: sideOfThumbnail,
|
||||
@@ -66,7 +64,7 @@ class AlbumColumnItemWidget extends StatelessWidget {
|
||||
);
|
||||
} else {
|
||||
return const NoThumbnailWidget(
|
||||
addBorder: false,
|
||||
addBorder: false,
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -94,9 +92,7 @@ class AlbumColumnItemWidget extends StatelessWidget {
|
||||
snapshot.data!,
|
||||
NumberFormat().format(snapshot.data!),
|
||||
),
|
||||
style: textTheme.small.copyWith(
|
||||
color: colorScheme.textMuted,
|
||||
),
|
||||
style: textTheme.miniMuted,
|
||||
);
|
||||
} else {
|
||||
if (snapshot.hasError) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "dart:async";
|
||||
import "dart:math";
|
||||
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:logging/logging.dart";
|
||||
@@ -29,6 +30,10 @@ class _AlbumHorizontalListState extends State<AlbumHorizontalList> {
|
||||
_collectionUpdatesSubscription;
|
||||
late Logger _logger;
|
||||
|
||||
static const maxThumbnailWidth = 224.0;
|
||||
static const crossAxisSpacing = 8.0;
|
||||
static const horizontalPadding = 16.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -47,6 +52,12 @@ class _AlbumHorizontalListState extends State<AlbumHorizontalList> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
final int albumsCountInRow = max(screenWidth ~/ maxThumbnailWidth, 3);
|
||||
final totalHorizontalPadding = (albumsCountInRow - 1) * crossAxisSpacing;
|
||||
final sideOfThumbnail =
|
||||
(screenWidth - totalHorizontalPadding - horizontalPadding) /
|
||||
albumsCountInRow;
|
||||
debugPrint('$runtimeType widget build');
|
||||
return FutureBuilder<List<Collection>>(
|
||||
future: widget.collectionsFuture(),
|
||||
@@ -75,20 +86,25 @@ class _AlbumHorizontalListState extends State<AlbumHorizontalList> {
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SizedBox(
|
||||
height: 147, //139 + 8 (calculated from figma design)
|
||||
child: ListView.separated(
|
||||
separatorBuilder: (context, index) =>
|
||||
const SizedBox(width: 4),
|
||||
height: sideOfThumbnail + 46,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: collections.length,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: horizontalPadding / 2,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final item = collections[index];
|
||||
return AlbumRowItemWidget(
|
||||
item,
|
||||
120,
|
||||
showFileCount: false,
|
||||
hasVerifiedLock: widget.hasVerifiedLock,
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: horizontalPadding / 2,
|
||||
),
|
||||
child: AlbumRowItemWidget(
|
||||
item,
|
||||
sideOfThumbnail,
|
||||
showFileCount: true,
|
||||
hasVerifiedLock: widget.hasVerifiedLock,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -36,9 +36,7 @@ class AlbumListItemWidget extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.horizontal(
|
||||
left: Radius.circular(4),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: SizedBox(
|
||||
height: sideOfThumbnail,
|
||||
width: sideOfThumbnail,
|
||||
@@ -53,7 +51,9 @@ class AlbumListItemWidget extends StatelessWidget {
|
||||
shouldShowOwnerAvatar: false,
|
||||
);
|
||||
} else {
|
||||
return const NoThumbnailWidget(addBorder: false);
|
||||
return const NoThumbnailWidget(
|
||||
addBorder: false,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -63,13 +63,15 @@ class AlbumListItemWidget extends StatelessWidget {
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
collection.displayName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
FutureBuilder<int>(
|
||||
future: CollectionsService.instance.getFileCount(collection),
|
||||
future:
|
||||
CollectionsService.instance.getFileCount(collection),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Text(
|
||||
@@ -123,7 +125,7 @@ class AlbumListItemWidget extends StatelessWidget {
|
||||
? colorScheme.strokeMuted
|
||||
: colorScheme.strokeFainter,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
||||
@@ -3,7 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
|
||||
///https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design?node-id=10854%3A57947&t=H5AvR79OYDnB9ekw-4
|
||||
//https://www.figma.com/design/SYtMyLBs5SAOkTbfMMzhqt/Ente-Visual-Design?node-id=39181-172209&t=3qmSZWpXF3ZC4JGN-1
|
||||
|
||||
class NewAlbumListItemWidget extends StatelessWidget {
|
||||
const NewAlbumListItemWidget({
|
||||
super.key,
|
||||
@@ -22,12 +23,13 @@ class NewAlbumListItemWidget extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.horizontal(
|
||||
left: Radius.circular(4),
|
||||
),
|
||||
child: SizedBox(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Container(
|
||||
height: sideOfThumbnail,
|
||||
width: sideOfThumbnail,
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? colorScheme.backdropBase
|
||||
: colorScheme.backdropFaint,
|
||||
child: Icon(
|
||||
Icons.add_outlined,
|
||||
color: colorScheme.strokeMuted,
|
||||
|
||||
@@ -23,6 +23,7 @@ class NewAlbumRowItemWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final result = await showTextInputDialog(
|
||||
@@ -67,25 +68,31 @@ class NewAlbumRowItemWidget extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: DottedBorder(
|
||||
borderType: BorderType.RRect,
|
||||
strokeWidth: 1.5,
|
||||
dashPattern: const [3.75, 3.75],
|
||||
radius: const Radius.circular(2.35),
|
||||
padding: EdgeInsets.zero,
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
height: height,
|
||||
width: width,
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? colorScheme.backdropBase
|
||||
: colorScheme.backdropFaint,
|
||||
child: DottedBorder(
|
||||
borderType: BorderType.RRect,
|
||||
strokeWidth: 1.75,
|
||||
dashPattern: const [3.75, 3.75],
|
||||
radius: const Radius.circular(12),
|
||||
padding: EdgeInsets.zero,
|
||||
color: colorScheme.strokeFaint,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: colorScheme.strokeFaint,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
S.of(context).addNew,
|
||||
style: getEnteTextTheme(context).smallFaint,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "package:figma_squircle/figma_squircle.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:intl/intl.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
@@ -24,6 +25,9 @@ class AlbumRowItemWidget extends StatelessWidget {
|
||||
final void Function(Collection)? onTapCallback;
|
||||
final void Function(Collection)? onLongPressCallback;
|
||||
final SelectedAlbums? selectedAlbums;
|
||||
static const _borderWidth = 1.0;
|
||||
static const _cornerRadius = 12.0;
|
||||
static const _cornerSmoothing = 1.0;
|
||||
|
||||
const AlbumRowItemWidget(
|
||||
this.c,
|
||||
@@ -56,125 +60,152 @@ class AlbumRowItemWidget extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
child: SizedBox(
|
||||
height: sideOfThumbnail,
|
||||
width: sideOfThumbnail,
|
||||
child: Stack(
|
||||
children: [
|
||||
FutureBuilder<EnteFile?>(
|
||||
future: CollectionsService.instance.getCover(c),
|
||||
builder: (context, snapshot) {
|
||||
EnteFile? thumbnail;
|
||||
if (snapshot.hasData) {
|
||||
thumbnail = snapshot.data!;
|
||||
} else {
|
||||
//Need to use cached thumbnail so that the hero
|
||||
//animation works as expected.
|
||||
thumbnail =
|
||||
CollectionsService.instance.getCoverCache(c);
|
||||
}
|
||||
if (thumbnail != null) {
|
||||
final bool isSelected =
|
||||
selectedAlbums?.isAlbumSelected(c) ?? false;
|
||||
final String heroTag = tagPrefix + thumbnail.tag;
|
||||
final thumbnailWidget = ThumbnailWidget(
|
||||
thumbnail,
|
||||
shouldShowArchiveStatus: isOwner
|
||||
? c.isArchived()
|
||||
: c.hasShareeArchived(),
|
||||
showFavForAlbumOnly: true,
|
||||
shouldShowSyncStatus: false,
|
||||
shouldShowPinIcon: isOwner && c.isPinned,
|
||||
key: Key(heroTag),
|
||||
);
|
||||
return Hero(
|
||||
tag: heroTag,
|
||||
transitionOnUserGestures: true,
|
||||
child: isSelected
|
||||
? ColorFiltered(
|
||||
colorFilter: ColorFilter.mode(
|
||||
Colors.black.withOpacity(
|
||||
0.4,
|
||||
),
|
||||
BlendMode.darken,
|
||||
),
|
||||
child: thumbnailWidget,
|
||||
)
|
||||
: thumbnailWidget,
|
||||
);
|
||||
} else {
|
||||
return const NoThumbnailWidget();
|
||||
}
|
||||
},
|
||||
),
|
||||
if (isOwner && (c.hasSharees || c.hasLink))
|
||||
Hero(
|
||||
tag: tagPrefix + "_sharees",
|
||||
transitionOnUserGestures: true,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: AlbumSharesIcons(
|
||||
padding: const EdgeInsets.only(left: 4, top: 4),
|
||||
sharees: c.getSharees(),
|
||||
type: AvatarType.mini,
|
||||
trailingWidget: linkIcon,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 5,
|
||||
right: 5,
|
||||
child: Hero(
|
||||
tag: tagPrefix + "_album_selection",
|
||||
transitionOnUserGestures: true,
|
||||
child: ListenableBuilder(
|
||||
listenable: selectedAlbums ?? ValueNotifier(false),
|
||||
builder: (context, _) {
|
||||
SizedBox(
|
||||
height: sideOfThumbnail,
|
||||
width: sideOfThumbnail,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ClipSmoothRect(
|
||||
radius: SmoothBorderRadius(
|
||||
cornerRadius: _cornerRadius + _borderWidth,
|
||||
cornerSmoothing: _cornerSmoothing,
|
||||
),
|
||||
child: Container(
|
||||
color: getEnteColorScheme(context).strokeFaint,
|
||||
width: sideOfThumbnail,
|
||||
height: sideOfThumbnail,
|
||||
),
|
||||
),
|
||||
ClipSmoothRect(
|
||||
radius: SmoothBorderRadius(
|
||||
cornerRadius: _cornerRadius,
|
||||
cornerSmoothing: _cornerSmoothing,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: sideOfThumbnail - _borderWidth * 2,
|
||||
width: sideOfThumbnail - _borderWidth * 2,
|
||||
child: Stack(
|
||||
children: [
|
||||
FutureBuilder<EnteFile?>(
|
||||
future: CollectionsService.instance.getCover(c),
|
||||
builder: (context, snapshot) {
|
||||
EnteFile? thumbnail;
|
||||
if (snapshot.hasData) {
|
||||
thumbnail = snapshot.data!;
|
||||
} else {
|
||||
//Need to use cached thumbnail so that the hero
|
||||
//animation works as expected.
|
||||
thumbnail =
|
||||
CollectionsService.instance.getCoverCache(c);
|
||||
}
|
||||
if (thumbnail != null) {
|
||||
final bool isSelected =
|
||||
selectedAlbums?.isAlbumSelected(c) ?? false;
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
child: isSelected
|
||||
? const Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Colors.white,
|
||||
size: 22,
|
||||
)
|
||||
: null,
|
||||
final String heroTag = tagPrefix + thumbnail.tag;
|
||||
final thumbnailWidget = ThumbnailWidget(
|
||||
thumbnail,
|
||||
shouldShowArchiveStatus: isOwner
|
||||
? c.isArchived()
|
||||
: c.hasShareeArchived(),
|
||||
showFavForAlbumOnly: true,
|
||||
shouldShowSyncStatus: false,
|
||||
shouldShowPinIcon: isOwner && c.isPinned,
|
||||
key: Key(heroTag),
|
||||
);
|
||||
},
|
||||
),
|
||||
return Hero(
|
||||
tag: heroTag,
|
||||
transitionOnUserGestures: true,
|
||||
child: isSelected
|
||||
? ColorFiltered(
|
||||
colorFilter: ColorFilter.mode(
|
||||
Colors.black.withOpacity(
|
||||
0.4,
|
||||
),
|
||||
BlendMode.darken,
|
||||
),
|
||||
child: thumbnailWidget,
|
||||
)
|
||||
: thumbnailWidget,
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
color: getEnteColorScheme(context).backdropBase,
|
||||
child: const NoThumbnailWidget(
|
||||
borderRadius: 12,
|
||||
addBorder: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
if (!isOwner)
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Hero(
|
||||
tag: tagPrefix + "_owner_other",
|
||||
if (isOwner && (c.hasSharees || c.hasLink))
|
||||
Hero(
|
||||
tag: tagPrefix + "_sharees",
|
||||
transitionOnUserGestures: true,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: UserAvatarWidget(
|
||||
c.owner,
|
||||
thumbnailView: true,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: AlbumSharesIcons(
|
||||
padding: const EdgeInsets.only(left: 4, top: 4),
|
||||
sharees: c.getSharees(),
|
||||
type: AvatarType.mini,
|
||||
trailingWidget: linkIcon,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 5,
|
||||
right: 5,
|
||||
child: Hero(
|
||||
tag: tagPrefix + "_album_selection",
|
||||
transitionOnUserGestures: true,
|
||||
child: ListenableBuilder(
|
||||
listenable:
|
||||
selectedAlbums ?? ValueNotifier(false),
|
||||
builder: (context, _) {
|
||||
final bool isSelected =
|
||||
selectedAlbums?.isAlbumSelected(c) ?? false;
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
child: isSelected
|
||||
? const Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Colors.white,
|
||||
size: 22,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (!isOwner)
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Hero(
|
||||
tag: tagPrefix + "_owner_other",
|
||||
transitionOnUserGestures: true,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: UserAvatarWidget(
|
||||
c.owner,
|
||||
thumbnailView: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const SizedBox(height: 6),
|
||||
Hero(
|
||||
tag: tagPrefix + "_title",
|
||||
transitionOnUserGestures: true,
|
||||
@@ -214,7 +245,7 @@ class AlbumRowItemWidget extends StatelessWidget {
|
||||
const SizedBox(height: 2),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: enteTextTheme.tinyMuted,
|
||||
style: enteTextTheme.miniMuted,
|
||||
children: [
|
||||
TextSpan(text: textCount),
|
||||
],
|
||||
|
||||
@@ -5,6 +5,7 @@ import "package:flutter/services.dart";
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/create_new_album_event.dart";
|
||||
import "package:photos/events/tab_changed_event.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import 'package:photos/models/collection/collection.dart';
|
||||
@@ -183,31 +184,35 @@ class _AlbumVerticalListWidgetState extends State<AlbumVerticalListWidget> {
|
||||
}
|
||||
|
||||
if (collection != null) {
|
||||
if (await _runCollectionAction(
|
||||
context,
|
||||
collection,
|
||||
showProgressDialog: false,
|
||||
)) {
|
||||
if (widget.actionType == CollectionActionType.restoreFiles) {
|
||||
showShortToast(
|
||||
context,
|
||||
'Restored files to album ' + albumName,
|
||||
);
|
||||
} else {
|
||||
showShortToast(
|
||||
context,
|
||||
"Album '" + albumName + "' created.",
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
|
||||
await _navigateToCollection(
|
||||
if (widget.enableSelection) {
|
||||
Bus.instance.fire(CreateNewAlbumEvent(collection));
|
||||
} else {
|
||||
if (await _runCollectionAction(
|
||||
context,
|
||||
collection,
|
||||
hasVerifiedLock: hasVerifiedLock,
|
||||
);
|
||||
showProgressDialog: false,
|
||||
)) {
|
||||
if (widget.actionType == CollectionActionType.restoreFiles) {
|
||||
showShortToast(
|
||||
context,
|
||||
'Restored files to album ' + albumName,
|
||||
);
|
||||
} else {
|
||||
showShortToast(
|
||||
context,
|
||||
"Album '" + albumName + "' created.",
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
|
||||
await _navigateToCollection(
|
||||
context,
|
||||
collection,
|
||||
hasVerifiedLock: hasVerifiedLock,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/create_new_album_event.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import 'package:photos/models/collection/collection.dart';
|
||||
import 'package:photos/models/selected_files.dart';
|
||||
@@ -34,6 +36,12 @@ enum CollectionActionType {
|
||||
moveToHiddenCollection,
|
||||
}
|
||||
|
||||
extension CollectionActionTypeExtension on CollectionActionType {
|
||||
bool get isHiddenAction =>
|
||||
this == CollectionActionType.moveToHiddenCollection ||
|
||||
this == CollectionActionType.addToHiddenAlbum;
|
||||
}
|
||||
|
||||
String _actionName(
|
||||
BuildContext context,
|
||||
CollectionActionType type,
|
||||
@@ -119,16 +127,29 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
|
||||
static const int okButtonSize = 80;
|
||||
String _searchQuery = "";
|
||||
final _selectedCollections = <Collection>[];
|
||||
final _recentlyCreatedCollections = <Collection>[];
|
||||
late StreamSubscription<CreateNewAlbumEvent> _createNewAlbumSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_showOnlyHiddenCollections =
|
||||
widget.actionType == CollectionActionType.moveToHiddenCollection ||
|
||||
widget.actionType == CollectionActionType.addToHiddenAlbum;
|
||||
_showOnlyHiddenCollections = widget.actionType.isHiddenAction;
|
||||
_enableSelection = (widget.actionType == CollectionActionType.addFiles ||
|
||||
widget.actionType == CollectionActionType.addToHiddenAlbum) &&
|
||||
(widget.sharedFiles == null || widget.sharedFiles!.isEmpty);
|
||||
_createNewAlbumSubscription =
|
||||
Bus.instance.on<CreateNewAlbumEvent>().listen((event) {
|
||||
setState(() {
|
||||
_recentlyCreatedCollections.insert(0, event.collection);
|
||||
_selectedCollections.add(event.collection);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_createNewAlbumSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -319,15 +340,25 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
|
||||
|
||||
Future<List<Collection>> _getCollections() async {
|
||||
if (_showOnlyHiddenCollections) {
|
||||
final List<Collection> recentlyCreated = [];
|
||||
final List<Collection> hidden = [];
|
||||
|
||||
final hiddenCollections = CollectionsService.instance
|
||||
.getHiddenCollections(includeDefaultHidden: false);
|
||||
hiddenCollections.sort((first, second) {
|
||||
for (final collection in hiddenCollections) {
|
||||
if (_recentlyCreatedCollections.contains(collection)) {
|
||||
recentlyCreated.add(collection);
|
||||
} else {
|
||||
hidden.add(collection);
|
||||
}
|
||||
}
|
||||
hidden.sort((first, second) {
|
||||
return compareAsciiLowerCaseNatural(
|
||||
first.displayName,
|
||||
second.displayName,
|
||||
);
|
||||
});
|
||||
return hiddenCollections;
|
||||
return recentlyCreated + hidden;
|
||||
} else {
|
||||
final List<Collection> collections =
|
||||
CollectionsService.instance.getCollectionsForUI(
|
||||
@@ -344,6 +375,7 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
|
||||
});
|
||||
final List<Collection> pinned = [];
|
||||
final List<Collection> unpinned = [];
|
||||
final List<Collection> recentlyCreated = [];
|
||||
// show uncategorized collection only for restore files action
|
||||
Collection? uncategorized;
|
||||
for (final collection in collections) {
|
||||
@@ -355,13 +387,20 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (_recentlyCreatedCollections.contains(collection)) {
|
||||
recentlyCreated.add(collection);
|
||||
continue;
|
||||
}
|
||||
if (collection.isPinned) {
|
||||
pinned.add(collection);
|
||||
} else {
|
||||
unpinned.add(collection);
|
||||
}
|
||||
}
|
||||
return (uncategorized != null ? [uncategorized] + pinned + unpinned : []);
|
||||
|
||||
return uncategorized != null
|
||||
? [uncategorized] + recentlyCreated + pinned + unpinned
|
||||
: recentlyCreated + pinned + unpinned;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "dart:async";
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:flutter_svg/flutter_svg.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/album_sort_order_change_event.dart";
|
||||
import "package:photos/events/collection_updated_event.dart";
|
||||
@@ -142,7 +143,7 @@ class _CollectionListPageState extends State<CollectionListPage> {
|
||||
|
||||
Widget _sortMenu(List<Collection> collections) {
|
||||
final colorTheme = getEnteColorScheme(context);
|
||||
|
||||
final isLightMode = Theme.of(context).brightness == Brightness.light;
|
||||
Widget sortOptionText(AlbumSortKey key) {
|
||||
String text = key.toString();
|
||||
switch (key) {
|
||||
@@ -180,12 +181,7 @@ class _CollectionListPageState extends State<CollectionListPage> {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButtonWidget(
|
||||
icon: albumViewType == AlbumViewType.grid
|
||||
? Icons.view_list_outlined
|
||||
: Icons.grid_view_outlined,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
iconColor: colorTheme.blurStrokePressed,
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
albumViewType = albumViewType == AlbumViewType.grid
|
||||
@@ -194,6 +190,24 @@ class _CollectionListPageState extends State<CollectionListPage> {
|
||||
});
|
||||
await localSettings.setAlbumViewType(albumViewType!);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: SizedBox(
|
||||
height: 24,
|
||||
width: 24,
|
||||
child: albumViewType == AlbumViewType.grid
|
||||
? SvgPicture.asset(
|
||||
isLightMode
|
||||
? "assets/icons/list_view_icon_light.svg"
|
||||
: "assets/icons/list_view_icon_dark.svg",
|
||||
)
|
||||
: Icon(
|
||||
Icons.grid_view,
|
||||
color: colorTheme.textMuted,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTapDown: (TapDownDetails details) async {
|
||||
@@ -226,9 +240,9 @@ class _CollectionListPageState extends State<CollectionListPage> {
|
||||
}
|
||||
},
|
||||
child: IconButtonWidget(
|
||||
icon: Icons.sort_outlined,
|
||||
icon: Icons.sort_rounded,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
iconColor: colorTheme.blurStrokePressed,
|
||||
iconColor: colorTheme.textMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import "package:figma_squircle/figma_squircle.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
import 'package:photos/models/device_collection.dart';
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import 'package:photos/ui/viewer/file/file_icons_widget.dart';
|
||||
import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
|
||||
import 'package:photos/ui/viewer/gallery/device_folder_page.dart';
|
||||
@@ -9,6 +11,11 @@ import 'package:photos/utils/navigation_util.dart';
|
||||
class DeviceFolderItem extends StatelessWidget {
|
||||
final DeviceCollection deviceCollection;
|
||||
final double sideOfThumbnail;
|
||||
|
||||
static const _cornerRadius = 12.0;
|
||||
static const _cornerSmoothing = 1.0;
|
||||
static const _borderWidth = 1.0;
|
||||
|
||||
const DeviceFolderItem(
|
||||
this.deviceCollection, {
|
||||
///120 is default for the 'on device' scrollview in albums section
|
||||
@@ -23,36 +30,60 @@ class DeviceFolderItem extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
child: SizedBox(
|
||||
height: sideOfThumbnail,
|
||||
width: sideOfThumbnail,
|
||||
child: Hero(
|
||||
tag: "device_folder:" +
|
||||
deviceCollection.name +
|
||||
deviceCollection.thumbnail!.tag,
|
||||
transitionOnUserGestures: true,
|
||||
child: Stack(
|
||||
children: [
|
||||
ThumbnailWidget(
|
||||
deviceCollection.thumbnail!,
|
||||
shouldShowSyncStatus: false,
|
||||
key: Key(
|
||||
"device_folder:" +
|
||||
deviceCollection.name +
|
||||
deviceCollection.thumbnail!.tag,
|
||||
SizedBox(
|
||||
height: sideOfThumbnail,
|
||||
width: sideOfThumbnail,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ClipSmoothRect(
|
||||
radius: SmoothBorderRadius(
|
||||
cornerRadius: _cornerRadius + _borderWidth,
|
||||
cornerSmoothing: _cornerSmoothing,
|
||||
),
|
||||
child: Container(
|
||||
color: getEnteColorScheme(context).strokeFaint,
|
||||
width: sideOfThumbnail,
|
||||
height: sideOfThumbnail,
|
||||
),
|
||||
),
|
||||
ClipSmoothRect(
|
||||
radius: SmoothBorderRadius(
|
||||
cornerRadius: _cornerRadius,
|
||||
cornerSmoothing: _cornerSmoothing,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: sideOfThumbnail - _borderWidth * 2,
|
||||
width: sideOfThumbnail - _borderWidth * 2,
|
||||
child: Hero(
|
||||
tag: "device_folder:" +
|
||||
deviceCollection.name +
|
||||
deviceCollection.thumbnail!.tag,
|
||||
transitionOnUserGestures: true,
|
||||
child: Stack(
|
||||
children: [
|
||||
ThumbnailWidget(
|
||||
deviceCollection.thumbnail!,
|
||||
shouldShowSyncStatus: false,
|
||||
key: Key(
|
||||
"device_folder:" +
|
||||
deviceCollection.name +
|
||||
deviceCollection.thumbnail!.tag,
|
||||
),
|
||||
),
|
||||
isBackedUp
|
||||
? const SizedBox.shrink()
|
||||
: const UnSyncedIcon(),
|
||||
],
|
||||
),
|
||||
),
|
||||
isBackedUp ? const SizedBox.shrink() : const UnSyncedIcon(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
width: sideOfThumbnail,
|
||||
child: Text(
|
||||
@@ -62,6 +93,17 @@ class DeviceFolderItem extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
SizedBox(
|
||||
width: sideOfThumbnail,
|
||||
child: Text(
|
||||
deviceCollection.count.toString(),
|
||||
textAlign: TextAlign.left,
|
||||
style:
|
||||
Theme.of(context).colorScheme.enteTheme.textTheme.miniMuted,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import "dart:math";
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -33,6 +34,9 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
|
||||
executionInterval: const Duration(seconds: 5),
|
||||
leading: true,
|
||||
);
|
||||
static const maxThumbnailWidth = 224.0;
|
||||
static const horizontalPadding = 16.0;
|
||||
static const crossAxisSpacing = 8.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -57,9 +61,18 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
final int albumsCountInCrossAxis = max(screenWidth ~/ maxThumbnailWidth, 3);
|
||||
|
||||
final double totalCrossAxisSpacing =
|
||||
(albumsCountInCrossAxis - 1) * crossAxisSpacing;
|
||||
final double sideOfThumbnail =
|
||||
(screenWidth - totalCrossAxisSpacing - horizontalPadding) /
|
||||
albumsCountInCrossAxis;
|
||||
|
||||
debugPrint("${(DeviceFoldersGridView).toString()} - $_loadReason");
|
||||
return SizedBox(
|
||||
height: 170,
|
||||
height: sideOfThumbnail + 46,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FutureBuilder<List<DeviceCollection>>(
|
||||
@@ -72,19 +85,28 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
|
||||
padding: EdgeInsets.all(22),
|
||||
child: EmptyState(),
|
||||
)
|
||||
: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||
physics: const ScrollPhysics(),
|
||||
// to disable GridView's scrolling
|
||||
itemBuilder: (context, index) {
|
||||
final deviceCollection = snapshot.data![index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
child: DeviceFolderItem(deviceCollection),
|
||||
);
|
||||
},
|
||||
itemCount: snapshot.data!.length,
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: horizontalPadding / 2,
|
||||
),
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const ScrollPhysics(),
|
||||
// to disable GridView's scrolling
|
||||
itemBuilder: (context, index) {
|
||||
final deviceCollection = snapshot.data![index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: horizontalPadding / 2,
|
||||
),
|
||||
child: DeviceFolderItem(
|
||||
deviceCollection,
|
||||
sideOfThumbnail: sideOfThumbnail,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: snapshot.data!.length,
|
||||
),
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
_logger.severe("failed to load device gallery", snapshot.error);
|
||||
|
||||
@@ -62,13 +62,12 @@ class _DeviceFolderVerticalGridViewBodyState
|
||||
executionInterval: const Duration(seconds: 4),
|
||||
);
|
||||
/*
|
||||
Aspect ratio 1:1 Max width 224 Fixed gap 8
|
||||
Width changes dynamically with screen width such that we can fit 2 in one row.
|
||||
Keep the width integral (center the albums to distribute excess pixels)
|
||||
*/
|
||||
static const maxThumbnailWidth = 170.0;
|
||||
static const fixedGapBetweenAlbum = 2.0;
|
||||
static const minGapForHorizontalPadding = 8.0;
|
||||
Aspect ratio 1:1
|
||||
Width changes dynamically with screen width
|
||||
*/
|
||||
static const maxThumbnailWidth = 224.0;
|
||||
static const horizontalPadding = 16.0;
|
||||
static const crossAxisSpacing = 8.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -101,30 +100,28 @@ class _DeviceFolderVerticalGridViewBodyState
|
||||
FilesDB.instance.getDeviceCollections(includeCoverThumbnail: true),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final double screenWidth = MediaQuery.of(context).size.width;
|
||||
final int albumsCountInOneRow =
|
||||
final double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
final int albumsCountInCrossAxis =
|
||||
max(screenWidth ~/ maxThumbnailWidth, 3);
|
||||
final double gapBetweenAlbums =
|
||||
(albumsCountInOneRow - 1) * fixedGapBetweenAlbum;
|
||||
final double gapOnSizeOfAlbums = minGapForHorizontalPadding +
|
||||
(screenWidth -
|
||||
gapBetweenAlbums -
|
||||
(2 * minGapForHorizontalPadding)) %
|
||||
albumsCountInOneRow;
|
||||
|
||||
final double totalCrossAxisSpacing =
|
||||
(albumsCountInCrossAxis - 1) * crossAxisSpacing;
|
||||
final double sideOfThumbnail =
|
||||
(screenWidth - gapOnSizeOfAlbums - gapBetweenAlbums) /
|
||||
albumsCountInOneRow;
|
||||
(screenWidth - totalCrossAxisSpacing - horizontalPadding) /
|
||||
albumsCountInCrossAxis;
|
||||
|
||||
return snapshot.data!.isEmpty
|
||||
? const SliverFillRemaining(child: EmptyState())
|
||||
: SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
padding: const EdgeInsets.only(
|
||||
left: horizontalPadding / 2,
|
||||
right: horizontalPadding / 2,
|
||||
),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: albumsCountInOneRow,
|
||||
mainAxisSpacing: 4,
|
||||
crossAxisSpacing: gapBetweenAlbums,
|
||||
crossAxisCount: albumsCountInCrossAxis,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: crossAxisSpacing,
|
||||
childAspectRatio:
|
||||
sideOfThumbnail / (sideOfThumbnail + 46),
|
||||
),
|
||||
|
||||
@@ -186,7 +186,7 @@ class _CollectionsFlexiGridViewWidgetState
|
||||
),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: albumsCountInCrossAxis,
|
||||
mainAxisSpacing: 2,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: CollectionsFlexiGridViewWidget.crossAxisSpacing,
|
||||
childAspectRatio: sideOfThumbnail / (sideOfThumbnail + 46),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_svg/flutter_svg.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/components/buttons/icon_button_widget.dart";
|
||||
|
||||
class SearchableAppBar extends StatefulWidget {
|
||||
final Widget title;
|
||||
@@ -56,6 +56,7 @@ class _SearchableAppBarState extends State<SearchableAppBar> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLightMode = Theme.of(context).brightness == Brightness.light;
|
||||
return SliverAppBar(
|
||||
floating: true,
|
||||
elevation: 0,
|
||||
@@ -81,11 +82,20 @@ class _SearchableAppBarState extends State<SearchableAppBar> {
|
||||
actions: _isSearchActive
|
||||
? null
|
||||
: [
|
||||
IconButtonWidget(
|
||||
icon: Icons.search,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
GestureDetector(
|
||||
onTap: _activateSearch,
|
||||
iconColor: getEnteColorScheme(context).blurStrokePressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: SizedBox(
|
||||
height: 18,
|
||||
width: 18,
|
||||
child: SvgPicture.asset(
|
||||
isLightMode
|
||||
? "assets/icons/search_icon_light.svg"
|
||||
: "assets/icons/search_icon_dark.svg",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
...?widget.actions,
|
||||
],
|
||||
|
||||
@@ -35,7 +35,7 @@ class QuickLinkAlbumItem extends StatelessWidget {
|
||||
isSelected ? colorScheme.strokeMuted : colorScheme.strokeFainter,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(2),
|
||||
Radius.circular(6),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
@@ -57,9 +57,7 @@ class QuickLinkAlbumItem extends StatelessWidget {
|
||||
return Hero(
|
||||
tag: heroTag,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.horizontal(
|
||||
left: Radius.circular(2),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: ThumbnailWidget(
|
||||
snapshot.data!,
|
||||
key: ValueKey(heroTag),
|
||||
|
||||
@@ -60,6 +60,10 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
|
||||
const Duration(milliseconds: 500),
|
||||
);
|
||||
|
||||
static const maxThumbnailWidth = 224.0;
|
||||
static const crossAxisSpacing = 8.0;
|
||||
static const horizontalPadding = 16.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -135,9 +139,14 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
|
||||
}
|
||||
|
||||
Widget _getSharedCollectionsGallery(SharedCollections collections) {
|
||||
const maxThumbnailWidth = 160.0;
|
||||
const maxQuickLinks = 4;
|
||||
final numberOfQuickLinks = collections.quickLinks.length;
|
||||
final double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
final int albumsCountInRow = max(screenWidth ~/ maxThumbnailWidth, 3);
|
||||
final totalHorizontalPadding = (albumsCountInRow - 1) * crossAxisSpacing;
|
||||
final sideOfThumbnail =
|
||||
(screenWidth - totalHorizontalPadding - horizontalPadding) /
|
||||
albumsCountInRow;
|
||||
const quickLinkTitleHeroTag = "quick_link_title";
|
||||
final SectionTitle sharedWithYou =
|
||||
SectionTitle(title: S.of(context).sharedWithYou);
|
||||
@@ -150,195 +159,188 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
|
||||
margin: const EdgeInsets.only(bottom: 50),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SectionOptions(
|
||||
onTap: collections.incoming.isNotEmpty
|
||||
? () {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
CollectionListPage(
|
||||
collections.incoming,
|
||||
sectionType:
|
||||
UISectionType.incomingCollections,
|
||||
tag: "incoming",
|
||||
appTitle: sharedWithYou,
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SectionOptions(
|
||||
onTap: collections.incoming.isNotEmpty
|
||||
? () {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
CollectionListPage(
|
||||
collections.incoming,
|
||||
sectionType:
|
||||
UISectionType.incomingCollections,
|
||||
tag: "incoming",
|
||||
appTitle: sharedWithYou,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
Hero(tag: "incoming", child: sharedWithYou),
|
||||
trailingWidget: collections.incoming.isNotEmpty
|
||||
? IconButtonWidget(
|
||||
icon: Icons.chevron_right,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
iconColor: colorTheme.blurStrokePressed,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
collections.incoming.isNotEmpty
|
||||
? SizedBox(
|
||||
height: sideOfThumbnail + 46,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: horizontalPadding / 2,
|
||||
),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: horizontalPadding / 2,
|
||||
),
|
||||
child: AlbumRowItemWidget(
|
||||
collections.incoming[index],
|
||||
sideOfThumbnail,
|
||||
tag: "incoming",
|
||||
showFileCount: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
Hero(tag: "incoming", child: sharedWithYou),
|
||||
trailingWidget: collections.incoming.isNotEmpty
|
||||
? IconButtonWidget(
|
||||
icon: Icons.chevron_right,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
iconColor: colorTheme.blurStrokePressed,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
collections.incoming.isNotEmpty
|
||||
? SizedBox(
|
||||
height: maxThumbnailWidth + 24,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: AlbumRowItemWidget(
|
||||
collections.incoming[index],
|
||||
maxThumbnailWidth,
|
||||
tag: "incoming",
|
||||
showFileCount: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: collections.incoming.length,
|
||||
),
|
||||
)
|
||||
: const IncomingAlbumEmptyState(),
|
||||
],
|
||||
),
|
||||
},
|
||||
itemCount: collections.incoming.length,
|
||||
),
|
||||
)
|
||||
: const IncomingAlbumEmptyState(),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SectionOptions(
|
||||
onTap: collections.outgoing.isNotEmpty
|
||||
? () {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
CollectionListPage(
|
||||
collections.outgoing,
|
||||
sectionType:
|
||||
UISectionType.outgoingCollections,
|
||||
tag: "outgoing",
|
||||
appTitle: sharedByYou,
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SectionOptions(
|
||||
onTap: collections.outgoing.isNotEmpty
|
||||
? () {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
CollectionListPage(
|
||||
collections.outgoing,
|
||||
sectionType:
|
||||
UISectionType.outgoingCollections,
|
||||
tag: "outgoing",
|
||||
appTitle: sharedByYou,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
Hero(tag: "outgoing", child: sharedByYou),
|
||||
trailingWidget: collections.outgoing.isNotEmpty
|
||||
? IconButtonWidget(
|
||||
icon: Icons.chevron_right,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
iconColor: colorTheme.blurStrokePressed,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
collections.outgoing.isNotEmpty
|
||||
? SizedBox(
|
||||
height: sideOfThumbnail + 46,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: horizontalPadding / 2,
|
||||
),
|
||||
child: AlbumRowItemWidget(
|
||||
collections.outgoing[index],
|
||||
sideOfThumbnail,
|
||||
tag: "outgoing",
|
||||
showFileCount: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
Hero(tag: "outgoing", child: sharedByYou),
|
||||
trailingWidget: collections.outgoing.isNotEmpty
|
||||
? IconButtonWidget(
|
||||
icon: Icons.chevron_right,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
iconColor: colorTheme.blurStrokePressed,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
collections.outgoing.isNotEmpty
|
||||
? SizedBox(
|
||||
height: maxThumbnailWidth + 24,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: AlbumRowItemWidget(
|
||||
collections.outgoing[index],
|
||||
maxThumbnailWidth,
|
||||
tag: "outgoing",
|
||||
showFileCount: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: collections.outgoing.length,
|
||||
),
|
||||
)
|
||||
: const OutgoingAlbumEmptyState(),
|
||||
],
|
||||
),
|
||||
},
|
||||
itemCount: collections.outgoing.length,
|
||||
),
|
||||
)
|
||||
: const OutgoingAlbumEmptyState(),
|
||||
],
|
||||
),
|
||||
numberOfQuickLinks > 0
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SectionOptions(
|
||||
onTap: numberOfQuickLinks > maxQuickLinks
|
||||
? () {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
AllQuickLinksPage(
|
||||
titleHeroTag: quickLinkTitleHeroTag,
|
||||
quickLinks: collections.quickLinks,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
Hero(
|
||||
tag: quickLinkTitleHeroTag,
|
||||
child: SectionTitle(
|
||||
title: S.of(context).quickLinks,
|
||||
),
|
||||
),
|
||||
trailingWidget: numberOfQuickLinks > maxQuickLinks
|
||||
? IconButtonWidget(
|
||||
icon: Icons.chevron_right,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
iconColor: colorTheme.blurStrokePressed,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final thumbnail = await CollectionsService
|
||||
.instance
|
||||
.getCover(collections.quickLinks[index]);
|
||||
final page = CollectionPage(
|
||||
CollectionWithThumbnail(
|
||||
collections.quickLinks[index],
|
||||
thumbnail,
|
||||
? Column(
|
||||
children: [
|
||||
SectionOptions(
|
||||
onTap: numberOfQuickLinks > maxQuickLinks
|
||||
? () {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
AllQuickLinksPage(
|
||||
titleHeroTag: quickLinkTitleHeroTag,
|
||||
quickLinks: collections.quickLinks,
|
||||
),
|
||||
tagPrefix: heroTagPrefix,
|
||||
);
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, page);
|
||||
},
|
||||
child: QuickLinkAlbumItem(
|
||||
c: collections.quickLinks[index],
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const SizedBox(height: 4);
|
||||
},
|
||||
itemCount: min(numberOfQuickLinks, maxQuickLinks),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
Hero(
|
||||
tag: quickLinkTitleHeroTag,
|
||||
child: SectionTitle(
|
||||
title: S.of(context).quickLinks,
|
||||
),
|
||||
],
|
||||
),
|
||||
trailingWidget: numberOfQuickLinks > maxQuickLinks
|
||||
? IconButtonWidget(
|
||||
icon: Icons.chevron_right,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
iconColor: colorTheme.blurStrokePressed,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
)
|
||||
const SizedBox(height: 2),
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final thumbnail = await CollectionsService
|
||||
.instance
|
||||
.getCover(collections.quickLinks[index]);
|
||||
final page = CollectionPage(
|
||||
CollectionWithThumbnail(
|
||||
collections.quickLinks[index],
|
||||
thumbnail,
|
||||
),
|
||||
tagPrefix: heroTagPrefix,
|
||||
);
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, page);
|
||||
},
|
||||
child: QuickLinkAlbumItem(
|
||||
c: collections.quickLinks[index],
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const SizedBox(height: 4);
|
||||
},
|
||||
itemCount: min(numberOfQuickLinks, maxQuickLinks),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
const SizedBox(height: 2),
|
||||
ValueListenableBuilder(
|
||||
@@ -368,7 +370,6 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
|
||||
: const EnteLoadingWidget();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const CollectPhotosCardWidget(),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:photos/models/collection/collection.dart';
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import "package:photos/services/sync/remote_sync_service.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import 'package:photos/ui/components/action_sheet_widget.dart';
|
||||
import 'package:photos/ui/components/buttons/button_widget.dart';
|
||||
import 'package:photos/ui/components/models/button_type.dart';
|
||||
@@ -59,16 +60,33 @@ class _DeleteEmptyAlbumsState extends State<DeleteEmptyAlbums> {
|
||||
}
|
||||
|
||||
Widget _buildDeleteButton() {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8.5, 4, 8, 12),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: ButtonWidget(
|
||||
buttonSize: ButtonSize.small,
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: S.of(context).deleteEmptyAlbums,
|
||||
icon: Icons.delete_sweep_outlined,
|
||||
shouldSurfaceExecutionStates: false,
|
||||
child: GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.fillFaint,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.delete_sweep_outlined, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
S.of(context).deleteEmptyAlbums,
|
||||
style: textTheme.smallBold,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
await showActionSheet(
|
||||
context: context,
|
||||
|
||||
@@ -3,14 +3,19 @@ import 'package:photos/theme/ente_theme.dart';
|
||||
|
||||
class NoThumbnailWidget extends StatelessWidget {
|
||||
final bool addBorder;
|
||||
const NoThumbnailWidget({this.addBorder = true, super.key});
|
||||
final double borderRadius;
|
||||
const NoThumbnailWidget({
|
||||
this.addBorder = true,
|
||||
this.borderRadius = 1,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final enteColorScheme = getEnteColorScheme(context);
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
border: addBorder
|
||||
? Border.all(
|
||||
color: enteColorScheme.strokeFaint,
|
||||
|
||||
@@ -129,8 +129,7 @@ class _AddLocationSheetState extends State<AddLocationSheet> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextInputWidget(
|
||||
hintText: S.of(context).locationName,
|
||||
borderRadius: 2,
|
||||
hintText: S.of(context).locationName,
|
||||
focusNode: _focusNode,
|
||||
submitNotifier: _submitNotifer,
|
||||
cancelNotifier: _cancelNotifier,
|
||||
|
||||
@@ -119,8 +119,7 @@ class _EditLocationSheetState extends State<EditLocationSheet> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextInputWidget(
|
||||
hintText: S.of(context).locationName,
|
||||
borderRadius: 2,
|
||||
hintText: S.of(context).locationName,
|
||||
focusNode: _focusNode,
|
||||
submitNotifier: _submitNotifer,
|
||||
cancelNotifier: _cancelNotifier,
|
||||
|
||||
@@ -32,7 +32,7 @@ class SearchThumbnailWidget extends StatelessWidget {
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.horizontal(left: Radius.circular(4)),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: file != null
|
||||
? (searchResult != null &&
|
||||
searchResult!.type() == ResultType.faces)
|
||||
|
||||
@@ -64,7 +64,7 @@ class SearchableItemWidget extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: colorScheme.strokeFainter),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(4),
|
||||
Radius.circular(6),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:dotted_border/dotted_border.dart";
|
||||
import "package:figma_squircle/figma_squircle.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/events/event.dart";
|
||||
@@ -156,9 +155,8 @@ class AlbumRecommendation extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipSmoothRect(
|
||||
radius:
|
||||
SmoothBorderRadius(cornerRadius: 2.35, cornerSmoothing: 1),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: SizedBox(
|
||||
width: _width,
|
||||
height: 100,
|
||||
@@ -171,10 +169,12 @@ class AlbumRecommendation extends StatelessWidget {
|
||||
shouldShowSyncStatus: false,
|
||||
),
|
||||
)
|
||||
: const NoThumbnailWidget(),
|
||||
: const NoThumbnailWidget(
|
||||
borderRadius: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
const SizedBox(height: 6),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: _width),
|
||||
child: Column(
|
||||
@@ -187,7 +187,7 @@ class AlbumRecommendation extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
const SizedBox(height: 2),
|
||||
FutureBuilder(
|
||||
future: CollectionsService.instance.getFileCount(
|
||||
albumSearchResult.collectionWithThumbnail.collection,
|
||||
@@ -198,7 +198,7 @@ class AlbumRecommendation extends StatelessWidget {
|
||||
snapshot.data != 0) {
|
||||
return Text(
|
||||
snapshot.data.toString(),
|
||||
style: enteTextTheme.smallMuted,
|
||||
style: enteTextTheme.miniMuted,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
@@ -228,20 +228,28 @@ class AlbumCTA extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DottedBorder(
|
||||
borderType: BorderType.RRect,
|
||||
strokeWidth: 1.5,
|
||||
borderPadding: const EdgeInsets.all(0.75),
|
||||
dashPattern: const [3.75, 3.75],
|
||||
radius: const Radius.circular(2.35),
|
||||
padding: EdgeInsets.zero,
|
||||
color: enteColorScheme.strokeFaint,
|
||||
child: SizedBox(
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? enteColorScheme.backdropBase
|
||||
: enteColorScheme.backdropFaint,
|
||||
height: 100,
|
||||
width: 100,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
child: DottedBorder(
|
||||
borderType: BorderType.RRect,
|
||||
strokeWidth: 1.5,
|
||||
borderPadding: const EdgeInsets.all(0.75),
|
||||
dashPattern: const [3.75, 3.75],
|
||||
radius: const Radius.circular(8),
|
||||
padding: EdgeInsets.zero,
|
||||
color: enteColorScheme.strokeFaint,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: enteColorScheme.strokeFaint,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user