Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Album support #106

Merged
merged 28 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
06775da
Set up album services
cp-pratik-k Dec 26, 2024
affdbf0
Merge branch 'refs/heads/main' into album-support
cp-pratik-k Dec 26, 2024
ccbb834
Merge branch 'refs/heads/main' into album-support
cp-pratik-k Dec 27, 2024
865fed6
Album support
cp-pratik-k Dec 31, 2024
65b8b79
test
cp-pratik-k Jan 1, 2025
c021f80
Merge branch 'refs/heads/main' into album-support
cp-pratik-k Jan 2, 2025
3b2e47f
Implement album support
cp-pratik-k Jan 3, 2025
73cccba
Update dependencies
cp-pratik-k Jan 3, 2025
3e314dd
Merge branch 'refs/heads/main' into album-support
cp-pratik-k Jan 3, 2025
db3077a
Improvement
cp-pratik-k Jan 3, 2025
70f33d8
Fix google drive album thumbnail load
cp-pratik-k Jan 7, 2025
67b14e6
Fix google drive album thumbnail
cp-pratik-k Jan 7, 2025
7210731
Add preview on albums
cp-pratik-k Jan 7, 2025
4a986ee
Album screen
cp-pratik-k Jan 8, 2025
7d91558
Album preview
cp-pratik-k Jan 8, 2025
dc8b12e
test
cp-pratik-k Jan 8, 2025
eb46e17
Fix pop error
cp-pratik-k Jan 8, 2025
5f72865
Fix hero animation
cp-sneha-s Jan 8, 2025
be70f2f
Add new selection menu
cp-pratik-k Jan 8, 2025
9da649e
Add new selection menu
cp-pratik-k Jan 8, 2025
068f0ad
push build
cp-pratik-k Jan 8, 2025
2f42437
publish build
cp-pratik-k Jan 8, 2025
2610407
Minor fix
cp-pratik-k Jan 8, 2025
8be4513
Album support
cp-pratik-k Jan 8, 2025
e14aa75
Album improvement
cp-pratik-k Jan 8, 2025
9d81f7f
Album support
cp-pratik-k Jan 8, 2025
33fcdf1
Dispose unused streams
cp-pratik-k Jan 9, 2025
f61793d
Update home list issue
cp-pratik-k Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions .idea/libraries/Flutter_Plugins.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions app/assets/locales/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"common_info":"Info",
"common_upload": "Upload",
"common_download": "Download",
"common_edit": "Edit",
"common_delete": "Delete",
"common_share": "Share",
"common_cancel": "Cancel",
Expand Down Expand Up @@ -116,6 +117,27 @@
"empty_media_title": "Oh Snap! No Media Found!",
"empty_media_message": "Looks like your gallery is taking a little break.",

"@_ALBUM":{},
"album_screen_title": "Albums",
"empty_album_title": "Oops! No Albums Here!",
"empty_album_message": "It seems like there are no albums to show right now. You can create a new one. We've got you covered!",

"@_ADD_ALBUM":{},
"add_album_screen_title": "Album",
"album_tame_field_title": "Album Name",
"store_in_title": "Store In",
"store_in_device_title": "Device",

cp-pratik-k marked this conversation as resolved.
Show resolved Hide resolved
"@_ALBUM_MEDIA_LIST":{},
"add_media_title": "Add Media",

"@_MEDIA_SELECTION":{},
"select_from_device_title": "Select from Device",
"select_from_google_drive_title": "Select from Google Drive",
"select_from_dropbox_title": "Select from Dropbox",
"no_media_access_title": "No cloud media access",
"no_cloud_media_access_message": "You don't have access to view media files, check you sign in with the cloud!",
cp-pratik-k marked this conversation as resolved.
Show resolved Hide resolved


"@_MEDIA_INFO":{},
"name_text": "Name",
Expand Down
116 changes: 116 additions & 0 deletions app/lib/components/app_media_thumbnail.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import 'package:data/models/media/media.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:style/extensions/context_extensions.dart';
import 'package:style/text/app_text_style.dart';
import '../domain/formatter/duration_formatter.dart';
import 'thumbnail_builder.dart';

class AppMediaThumbnail extends StatelessWidget {
final AppMedia media;
final String heroTag;
final void Function()? onTap;
final void Function()? onLongTap;
final bool selected;

const AppMediaThumbnail({
super.key,
required this.media,
required this.heroTag,
this.onTap,
this.onLongTap,
this.selected = false,
});

@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) => GestureDetector(
onTap: onTap,
onLongPress: onLongTap,
child: Stack(
alignment: Alignment.bottomLeft,
children: [
AnimatedOpacity(
curve: Curves.easeInOut,
duration: const Duration(milliseconds: 100),
opacity: selected ? 0.6 : 1,
child: AppMediaImage(
radius: selected ? 4 : 0,
size: constraints.biggest,
media: media,
heroTag: "$heroTag${media.toString()}",
),
),
if (media.type.isVideo) _videoDuration(context),
if (selected)
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.all(4),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: context.colorScheme.primary,
),
child: const Icon(
CupertinoIcons.checkmark_alt,
color: Colors.white,
size: 14,
),
),
),
],
),
),
);
}

Widget _videoDuration(BuildContext context) => Align(
alignment: Alignment.topCenter,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
stops: [0.0, 0.9],
begin: Alignment.topRight,
end: Alignment.bottomRight,
colors: [
Colors.black.withValues(alpha: 0.4),
Colors.transparent,
],
),
),
padding: const EdgeInsets.all(4).copyWith(bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
(media.videoDuration ?? Duration.zero).format,
style: AppTextStyles.caption.copyWith(
color: Colors.white,
fontSize: 12,
shadows: [
Shadow(
color: Colors.grey.shade400,
blurRadius: 5,
),
],
),
),
const SizedBox(width: 2),
Icon(
CupertinoIcons.play_fill,
color: Colors.white,
size: 14,
shadows: [
Shadow(
color: Colors.grey.shade400,
blurRadius: 5,
),
],
),
],
),
),
);
}
11 changes: 7 additions & 4 deletions app/lib/components/app_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class AppPage extends StatelessWidget {
final Widget? body;
final Widget Function(BuildContext context)? bodyBuilder;
final bool automaticallyImplyLeading;
final bool? resizeToAvoidBottomInset;
final bool resizeToAvoidBottomInset;
final Color? backgroundColor;
final Color? barBackgroundColor;

Expand All @@ -24,7 +24,7 @@ class AppPage extends StatelessWidget {
this.leading,
this.body,
this.floatingActionButton,
this.resizeToAvoidBottomInset,
this.resizeToAvoidBottomInset = true,
this.bodyBuilder,
this.automaticallyImplyLeading = true,
this.barBackgroundColor,
Expand All @@ -50,6 +50,7 @@ class AppPage extends StatelessWidget {
leading: leading,
middle: titleWidget ?? _title(context),
border: null,
enableBackgroundFilterBlur: false,
trailing: actions == null
? null
: actions!.length == 1
Expand All @@ -63,7 +64,7 @@ class AppPage extends StatelessWidget {
? MaterialLocalizations.of(context).backButtonTooltip
: null,
),
resizeToAvoidBottomInset: resizeToAvoidBottomInset ?? true,
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
backgroundColor: backgroundColor,
child: Stack(
alignment: Alignment.bottomRight,
Expand All @@ -89,6 +90,7 @@ class AppPage extends StatelessWidget {
leading == null
? null
: AppBar(
centerTitle: true,
backgroundColor: barBackgroundColor,
title: titleWidget ?? _title(context),
actions: [...?actions, const SizedBox(width: 16)],
Expand Down Expand Up @@ -150,7 +152,8 @@ class AdaptiveAppBar extends StatelessWidget {
: Column(
children: [
AppBar(
backgroundColor: context.colorScheme.barColor,
centerTitle: true,
backgroundColor: context.colorScheme.surface,
leading: leading,
actions: actions,
automaticallyImplyLeading: automaticallyImplyLeading,
Expand Down
1 change: 1 addition & 0 deletions app/lib/components/app_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Future<T?> showAppSheet<T>({
required Widget child,
}) {
return showModalBottomSheet(
useRootNavigator: true,
backgroundColor: context.colorScheme.containerNormalOnSurface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(22),
Expand Down
1 change: 1 addition & 0 deletions app/lib/ui/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class _CloudGalleryAppState extends ConsumerState<CloudGalleryApp> {
_handleNotification();

_router = GoRouter(
navigatorKey: rootNavigatorKey,
initialLocation: _configureInitialRoute(),
routes: $appRoutes,
redirect: (context, state) {
Expand Down
137 changes: 137 additions & 0 deletions app/lib/ui/flow/albums/add/add_album_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import 'package:data/models/album/album.dart';
import 'package:data/models/media/media.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:style/buttons/action_button.dart';
import 'package:style/extensions/context_extensions.dart';
import 'package:style/text/app_text_field.dart';
import 'package:style/text/app_text_style.dart';
import '../../../../components/app_page.dart';
import '../../../../components/snack_bar.dart';
import '../../../../domain/extensions/context_extensions.dart';
import 'add_album_state_notifier.dart';
import 'package:style/buttons/radio_selection_button.dart';

class AddAlbumScreen extends ConsumerStatefulWidget {
final Album? editAlbum;

const AddAlbumScreen({super.key, this.editAlbum});

@override
ConsumerState<AddAlbumScreen> createState() => _AddAlbumScreenState();
}

class _AddAlbumScreenState extends ConsumerState<AddAlbumScreen> {
late AutoDisposeStateNotifierProvider<AddAlbumStateNotifier, AddAlbumsState>
_provider;
late AddAlbumStateNotifier _notifier;

@override
void initState() {
_provider = addAlbumStateNotifierProvider(widget.editAlbum);
_notifier = ref.read(_provider.notifier);
super.initState();
}

void _observeError(BuildContext context) {
ref.listen(
_provider.select(
(value) => value.error,
), (previous, error) {
if (error != null) {
showErrorSnackBar(context: context, error: error);
}
});
}
cp-pratik-k marked this conversation as resolved.
Show resolved Hide resolved

void _observeSucceed(BuildContext context) {
ref.listen(
_provider.select(
(value) => value.succeed,
), (previous, success) {
if (success) {
context.pop(true);
}
});
}

@override
Widget build(BuildContext context) {
_observeError(context);
_observeSucceed(context);
final state = ref.watch(_provider);
return AppPage(
resizeToAvoidBottomInset: false,
title: context.l10n.add_album_screen_title,
body: _body(context: context, state: state),
actions: [
ActionButton(
onPressed: state.allowSave ? _notifier.createAlbum : null,
icon: Icon(
Icons.check,
size: 24,
color: state.allowSave
? context.colorScheme.textPrimary
: context.colorScheme.textDisabled,
),
),
],
);
}

Widget _body({required BuildContext context, required AddAlbumsState state}) {
return ListView(
children: [
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AppTextField(
controller: state.albumNameController,
onChanged: _notifier.validateAlbumName,
label: context.l10n.album_tame_field_title,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Fix typo in localization key: album_tame_field_title should be album_name_field_title

The localization key album_tame_field_title appears to be a typo. While it's defined in the localization file and used consistently, the word "tame" doesn't make semantic sense in this context. The field is clearly meant for the album name, so the key should follow the conventional naming pattern and be album_name_field_title.

  • app/lib/ui/flow/albums/add/add_album_screen.dart:99: Update the key to album_name_field_title
  • app/assets/locales/app_en.arb: Update the key from album_tame_field_title to album_name_field_title
🔗 Analysis chain

Double-check localization key.

The key album_tame_field_title might be a typo or mistranslation. Verify that you intended "album_name_field_title" or a similar phrase to avoid confusion in your localization strings.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Searching for 'album_tame_field_title' references in the codebase
rg --context 3 'album_tame_field_title'

Length of output: 1056

),
),
if ((state.googleAccount != null || state.dropboxAccount != null) &&
widget.editAlbum == null) ...[
const SizedBox(height: 40),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
context.l10n.store_in_title,
style: AppTextStyles.subtitle1.copyWith(
color: context.colorScheme.textPrimary,
),
),
),
const SizedBox(height: 8),
Column(
children: [
RadioSelectionButton<AppMediaSource>(
value: AppMediaSource.local,
groupValue: state.mediaSource,
onTab: () => _notifier.onSourceChange(AppMediaSource.local),
label: context.l10n.store_in_device_title,
),
if (state.googleAccount != null)
RadioSelectionButton<AppMediaSource>(
value: AppMediaSource.googleDrive,
groupValue: state.mediaSource,
onTab: () =>
_notifier.onSourceChange(AppMediaSource.googleDrive),
label: context.l10n.common_google_drive,
),
if (state.dropboxAccount != null)
RadioSelectionButton<AppMediaSource>(
value: AppMediaSource.dropbox,
groupValue: state.mediaSource,
onTab: () => _notifier.onSourceChange(AppMediaSource.dropbox),
label: context.l10n.common_dropbox,
),
],
),
],
],
);
}
}
Loading
Loading