git_operations 0.0.1
git_operations: ^0.0.1 copied to clipboard
Git operation workflows built on path_utils command execution.
import 'package:flutter/material.dart';
import 'package:git_operations/git_operations.dart';
import 'documentation/git_docs.dart';
import 'git_commands/git_command_demo.dart';
import 'pages/docs_browser_page.dart';
void main() {
runApp(const GitOperationsExampleApp());
}
class GitOperationsExampleApp extends StatelessWidget {
const GitOperationsExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'git_operations example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.light,
),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.dark,
),
useMaterial3: true,
),
themeMode: ThemeMode.system,
home: const DemoPage(),
);
}
}
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
late Future<_RepoInfo> _repoInfo;
int _selectedTab = 0;
@override
void initState() {
super.initState();
_repoInfo = _loadRepoInfo();
}
Future<_RepoInfo> _loadRepoInfo() async {
if (!isGitCommandsAvailable) {
return const _RepoInfo(
name: 'git_operations',
path: '(desktop only)',
branch: null,
);
}
final path = await resolveRepoPath();
final branch = await resolveCurrentBranch(path);
final segments = path.split(RegExp(r'[/\\]'));
return _RepoInfo(
name: segments.isEmpty ? path : segments.last,
path: path,
branch: branch,
);
}
void _refresh() {
final refreshedRepoInfo = _loadRepoInfo();
setState(() {
_repoInfo = refreshedRepoInfo;
});
}
@override
Widget build(BuildContext context) {
return FutureBuilder<_RepoInfo>(
future: _repoInfo,
builder: (context, snapshot) {
final repoInfo = snapshot.data;
return Scaffold(
appBar: AppBar(
title: const Text('git_operations example'),
actions: [
IconButton(
onPressed: _refresh,
tooltip: 'Refresh',
icon: const Icon(Icons.refresh),
),
],
),
body: snapshot.hasError
? Center(child: Text('Error: ${snapshot.error}'))
: repoInfo == null
? const Center(child: CircularProgressIndicator())
: _selectedTab == 0
? _RepositoryOverview(repoInfo: repoInfo)
: DocsBrowserPage(repoPath: repoInfo.path),
bottomNavigationBar: NavigationBar(
selectedIndex: _selectedTab,
onDestinationSelected: (index) {
setState(() => _selectedTab = index);
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Repository',
),
NavigationDestination(
icon: Icon(Icons.menu_book_outlined),
selectedIcon: Icon(Icons.menu_book),
label: 'Docs',
),
],
),
);
},
);
}
}
class _RepositoryOverview extends StatelessWidget {
const _RepositoryOverview({required this.repoInfo});
final _RepoInfo repoInfo;
@override
Widget build(BuildContext context) {
final repo = _ExampleRepo(
name: repoInfo.name,
path: repoInfo.path,
branch: repoInfo.branch,
);
return ListView(
padding: const EdgeInsets.all(16),
children: [
Text(
'Package overview',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
Text(
'This app demonstrates the GitRepository contract, runs git commands through '
'git_operations GitCommandRunner, and browses upstream git/git man pages from GitHub.',
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 24),
_InfoCard(
title: 'Repository',
value: repo.name,
),
_InfoCard(
title: 'Expanded path',
value: repo.getExpandedPath((_) {}),
),
_InfoCard(
title: 'Current branch',
value: repo.currentBranchOnDisk ?? '(unknown)',
),
_InfoCard(
title: 'Command docs source',
value: '${gitDocsSource.repositorySlug} @ ${gitDocsSource.branch}',
),
const SizedBox(height: 16),
Text(
'Use the Docs tab to search git/git man pages. Commands with a Run button '
'can be executed directly against this repository.',
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
Text(
'GitOperationService also provides checkout, stash, and pull workflows with '
'user dialogs for untracked and dirty working directories.',
style: Theme.of(context).textTheme.bodyMedium,
),
],
);
}
}
class _RepoInfo {
const _RepoInfo({
required this.name,
required this.path,
required this.branch,
});
final String name;
final String path;
final String? branch;
}
class _ExampleRepo implements GitRepository {
const _ExampleRepo({
required this.name,
required this.path,
this.branch,
});
@override
final String name;
final String path;
final String? branch;
@override
String? get currentBranchOnDisk => branch;
@override
String getExpandedPath(void Function(String message) log) => path;
}
class _InfoCard extends StatelessWidget {
const _InfoCard({required this.title, required this.value});
final String title;
final String value;
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
title: Text(title),
subtitle: SelectableText(
value,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontFamily: 'monospace',
),
),
),
);
}
}