Files
PilotVault/Fly App/lib/ui/album_page.dart
T
tajniak81andClaude Opus 4.8 afc6952eda Initial commit: PilotVault multi-service project
Add API Server (Go/PocketBase), Web App (Go BFF + Vue), Fly App
(Flutter/DJI MSDK), Adobe Plugin, and Docker/Docker AIO deployment
configs. Design assets and build artifacts are gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 11:43:33 +02:00

146 lines
4.9 KiB
Dart

import 'package:flutter/material.dart';
import '../theme.dart';
/// Portrait media grid — mirrors the ui_kit/fly "Album" mockup. Media is
/// placeholder content (the app has no on-device gallery source yet); the
/// layout, filters and badges match the design.
class AlbumPage extends StatefulWidget {
const AlbumPage({super.key});
@override
State<AlbumPage> createState() => _AlbumPageState();
}
class _AlbumPageState extends State<AlbumPage> {
static const List<String> _filters = <String>['All', 'Photos', 'Videos', 'Pano'];
int _active = 0;
// (isVideo, duration) — placeholder set from the mockup.
static const List<(bool, String?)> _media = <(bool, String?)>[
(true, '0:24'), (false, null), (false, null),
(true, '1:12'), (false, null), (false, null),
(false, null), (true, '0:08'), (false, null),
(false, null), (true, '0:31'), (false, null),
];
@override
Widget build(BuildContext context) {
final PVScheme s = PVScheme.of(context);
return Scaffold(
backgroundColor: s.bgApp,
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_header(s),
_filterBar(s),
const SizedBox(height: 14),
Expanded(child: _grid(s)),
],
),
),
);
}
Widget _header(PVScheme s) {
return Padding(
padding: const EdgeInsets.fromLTRB(12, 6, 20, 12),
child: Row(
children: <Widget>[
IconButton(
onPressed: () => Navigator.of(context).maybePop(),
icon: Icon(Icons.chevron_left, size: 26, color: s.textSecondary),
),
Text(
'Album',
style: TextStyle(fontFamily: PV.fontSans, fontSize: 20, fontWeight: FontWeight.w700, letterSpacing: -0.4, color: s.textPrimary),
),
const Spacer(),
Text('${_media.length} items', style: TextStyle(fontFamily: PV.fontMono, fontSize: 12, color: s.textTertiary)),
],
),
);
}
Widget _filterBar(PVScheme s) {
return SizedBox(
height: 28,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 20),
itemCount: _filters.length,
separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (BuildContext context, int i) {
final bool active = i == _active;
return GestureDetector(
onTap: () => setState(() => _active = i),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
alignment: Alignment.center,
decoration: BoxDecoration(
color: active ? s.accent : s.surfaceInset,
borderRadius: BorderRadius.circular(999),
),
child: Text(
_filters[i],
style: TextStyle(
fontFamily: PV.fontSans,
fontSize: 12.5,
fontWeight: FontWeight.w600,
color: active ? Colors.white : s.textSecondary,
),
),
),
);
},
),
);
}
Widget _grid(PVScheme s) {
return GridView.builder(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 6,
mainAxisSpacing: 6,
),
itemCount: _media.length,
itemBuilder: (BuildContext context, int i) {
final (bool isVideo, String? dur) = _media[i];
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[s.surface2, s.surfaceInset],
transform: GradientRotation((140 + i * 14) * 3.1415926 / 180),
),
border: Border.all(color: s.border),
borderRadius: BorderRadius.circular(10),
),
child: Stack(
children: <Widget>[
Center(child: Icon(isVideo ? Icons.videocam_outlined : Icons.image_outlined, size: 20, color: s.textTertiary)),
if (dur != null)
Positioned(
right: 6,
bottom: 5,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
decoration: BoxDecoration(color: const Color(0xB30B1730), borderRadius: BorderRadius.circular(5)),
child: Text(dur, style: const TextStyle(fontFamily: PV.fontMono, fontSize: 9.5, color: Colors.white)),
),
),
],
),
),
);
},
);
}
}