flutter_jira_issue_collector 0.1.0
flutter_jira_issue_collector: ^0.1.0 copied to clipboard
Jira Issue Collector for Flutter. Fetch collector fields dynamically, show customizable forms, or submit issues programmatically in the background.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_jira_issue_collector/flutter_jira_issue_collector.dart';
void main() {
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Jira Issue Collector Example',
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late final JiraIssueCollector _collector;
@override
void initState() {
super.initState();
_collector = JiraIssueCollector(
config: const JiraCollectorConfig(
baseUrl: 'https://jira.bpo-hq.com',
collectorId: '3e3ffaf3',
locale: 'en_US',
),
);
}
@override
void dispose() {
_collector.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Jira Collector Example')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// --- UI Mode: Full-screen dialog ---
_SectionCard(
title: 'UI Mode — Full-screen Dialog',
description:
'Opens the issue collector form as a full-screen dialog. '
'Fields are fetched dynamically from Jira.',
buttonLabel: 'Open Dialog',
onPressed: () => _showDialog(context),
),
const SizedBox(height: 16),
// --- UI Mode: Bottom sheet ---
_SectionCard(
title: 'UI Mode — Bottom Sheet',
description:
'Opens the issue collector as a draggable bottom sheet.',
buttonLabel: 'Open Bottom Sheet',
onPressed: () => _showBottomSheet(context),
),
const SizedBox(height: 16),
// --- UI Mode: With prefill + hidden fields ---
_SectionCard(
title: 'UI Mode — Prefilled + Hidden Fields',
description:
'Opens the form with email prefilled and hidden, so the '
'user does not see or modify it.',
buttonLabel: 'Open Prefilled',
onPressed: () => _showPrefilled(context),
),
const SizedBox(height: 16),
// --- Background Mode ---
_SectionCard(
title: 'Background Mode — Programmatic Submit',
description:
'Submits an issue silently in the background without any UI.',
buttonLabel: 'Submit in Background',
onPressed: () => _submitBackground(context),
),
const SizedBox(height: 16),
// --- Inspect Fields ---
_SectionCard(
title: 'Inspect Available Fields',
description:
'Fetches and displays the fields available in the collector, '
'useful for building your background submission payload.',
buttonLabel: 'Fetch Fields',
onPressed: () => _inspectFields(context),
),
],
),
);
}
Future<void> _showDialog(BuildContext context) async {
final result = await _collector.showCollector(
context,
onResult: (r) => _showSnackBar(context, r),
);
if (result != null && context.mounted) {
_showSnackBar(context, result);
}
}
Future<void> _showBottomSheet(BuildContext context) async {
final result = await _collector.showCollectorBottomSheet(
context,
onResult: (r) => _showSnackBar(context, r),
);
if (result != null && context.mounted) {
_showSnackBar(context, result);
}
}
Future<void> _showPrefilled(BuildContext context) async {
final result = await _collector.showCollector(
context,
prefillValues: {
'email': 'user@example.com',
'fullname': 'John Doe',
},
hiddenFieldIds: {'email', 'fullname'},
onResult: (r) => _showSnackBar(context, r),
);
if (result != null && context.mounted) {
_showSnackBar(context, result);
}
}
Future<void> _submitBackground(BuildContext context) async {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Submitting issue in background...')),
);
final result = await _collector.submitInBackground(
fieldValues: {
'summary': 'Auto-reported issue from Flutter app',
'description':
'This issue was submitted programmatically from the Flutter app.',
'email': 'flutter-app@example.com',
},
);
if (!context.mounted) return;
_showSnackBar(context, result);
}
Future<void> _inspectFields(BuildContext context) async {
try {
final fields = await _collector.fetchFields();
if (!context.mounted) return;
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Available Fields'),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: fields.length,
itemBuilder: (ctx, i) {
final f = fields[i];
return ListTile(
title: Text(f.label),
subtitle: Text(
'id: ${f.id} | type: ${f.type.name} | '
'required: ${f.required}',
),
dense: true,
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('Close'),
),
],
),
);
} catch (e) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error fetching fields: $e')),
);
}
}
void _showSnackBar(BuildContext context, SubmissionResult result) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
result.success
? 'Issue submitted! ${result.issueKey ?? ''}'
: 'Failed: ${result.errorMessage}',
),
backgroundColor: result.success ? Colors.green : Colors.red,
),
);
}
}
class _SectionCard extends StatelessWidget {
final String title;
final String description;
final String buttonLabel;
final VoidCallback onPressed;
const _SectionCard({
required this.title,
required this.description,
required this.buttonLabel,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Text(description),
const SizedBox(height: 12),
ElevatedButton(
onPressed: onPressed,
child: Text(buttonLabel),
),
],
),
),
);
}
}