From 1716cf45d0e4e41c7833105dab10542f5ed5dcf1 Mon Sep 17 00:00:00 2001 From: Mica White Date: Sun, 8 Feb 2026 10:10:24 -0500 Subject: Initial commit --- lib/main.dart | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 lib/main.dart (limited to 'lib/main.dart') diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..d35e631 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,298 @@ +import 'dart:typed_data'; + +import 'package:dadtuber/date_utils.dart'; +import 'package:dadtuber/index_file.dart'; +import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'DadTuber', + theme: ThemeData( + // This is the theme of your application. + // + // TRY THIS: Try running your application with "flutter run". You'll see + // the application has a purple toolbar. Then, without quitting the app, + // try changing the seedColor in the colorScheme below to Colors.green + // and then invoke "hot reload" (save your changes or press the "hot + // reload" button in a Flutter-supported IDE, or press "r" if you used + // the command line to start the app). + // + // Notice that the counter didn't reset back to zero; the application + // state is not lost during the reload. To reset the state, use hot + // restart instead. + // + // This works for code too, not just values: Most code changes can be + // tested with just a hot reload. + colorScheme: ColorScheme.fromSeed(seedColor: Colors.red), + useMaterial3: true, + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom(minimumSize: Size.fromRadius(24)), + ), + ), + home: const MyHomePage(title: 'DadTuber'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + late Future _projectIndexBuilder; + + @override + void initState() { + super.initState(); + + builder() async { + final dir = await getApplicationDocumentsDirectory(); + final path = "${dir.path}/index"; + + try { + return ProjectIndex.load(path); + } catch (_) { + return ProjectIndex.blank(); + } + } + + _projectIndexBuilder = builder(); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // TRY THIS: Try changing the color here to a specific color (to + // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar + // change color while the other colors stay the same. + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: FocusTraversalGroup( + policy: WidgetOrderTraversalPolicy(), + child: OrientationBuilder( + builder: (context, orientation) => + orientation == Orientation.landscape + ? Container( + padding: EdgeInsets.all(24), + child: Row( + spacing: 48, + children: [ + CreateNewColumn(showHeader: true), + Expanded( + child: SavedProjectsColumn( + projectIndexBuilder: _projectIndexBuilder, + showHeader: true, + ), + ), + ], + ), + ) + : Column( + spacing: 16, + children: [ + CreateNewColumn(showHeader: false), + Expanded( + child: SavedProjectsColumn( + projectIndexBuilder: _projectIndexBuilder, + showHeader: false, + ), + ), + ], + ), + ), + ), + ); + } +} + +class CreateNewColumn extends StatelessWidget { + final bool showHeader; + + const CreateNewColumn({required this.showHeader, super.key}); + + @override + Widget build(BuildContext context) { + return Column( + spacing: 12, + children: [ + showHeader + ? Text( + "Create New", + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ) + : SizedBox(), + ElevatedButton.icon( + icon: Icon(Icons.video_file), + onPressed: () => (), + label: Text('Create a new video'), + ), + ElevatedButton.icon( + icon: Icon(Icons.image), + onPressed: () => (), + label: Text('Create a new image'), + ), + ElevatedButton.icon( + icon: Icon(Icons.audio_file), + onPressed: () => (), + label: Text('Create a new sound'), + ), + ], + ); + } +} + +class SavedProjectsColumn extends StatelessWidget { + final bool showHeader; + final Future projectIndexBuilder; + + const SavedProjectsColumn({ + required this.projectIndexBuilder, + required this.showHeader, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: Text( + "Saved Projects", + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + ), + Expanded( + child: FutureBuilder( + future: projectIndexBuilder, + builder: (cx, snapshot) { + if (snapshot.hasData) { + return SavedProjectsListView(snapshot.data!); + } else if (snapshot.hasError) { + return Center(child: Text("An error occurred")); + } else { + return Center(child: CircularProgressIndicator()); + } + }, + ), + ), + ], + ); + } +} + +class SavedProjectsListView extends StatelessWidget { + final ProjectIndex _projectIndex; + + const SavedProjectsListView(this._projectIndex, {super.key}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + addAutomaticKeepAlives: false, + itemCount: _projectIndex.projects.length, + itemBuilder: (cx, idx) { + final project = _projectIndex.projects[idx]; + return SavedProjectTile( + title: project.title, + image: _projectIndex.getImage(project.title), + lastModified: project.lastModified, + projectType: project.type, + ); + }, + ); + } +} + +class SavedProjectTile extends StatelessWidget { + final String title; + final Uint8List? image; + final DateTime lastModified; + final ProjectType projectType; + + const SavedProjectTile({ + required this.title, + this.image, + required this.lastModified, + required this.projectType, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: SavedProjectThumbnail(projectType: projectType, image: image), + title: Text( + title, + overflow: TextOverflow.ellipsis, + ), + subtitle: Text('Edited ${daysAgo(lastModified)}'), + trailing: Text(projectType.normalString()), + onTap: () => (), + ); + } +} + +class SavedProjectThumbnail extends StatelessWidget { + final ProjectType projectType; + final Uint8List? image; + + const SavedProjectThumbnail({ + required this.projectType, + required this.image, + super.key, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 96, + height: 54, + child: Stack( + children: [ + image != null + ? Image.memory(image!, width: 80, height: 45) + : SizedBox(width: 80, height: 45), + Center( + child: Opacity(opacity: 0.5, child: Icon(projectType.icon())), + ), + ], + ), + ); + } +} -- cgit v1.2.3