lain-the-slayer 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lain/__init__.py +1 -0
- lain/cli.py +117 -0
- lain/practicals/01_hello_world.dart +25 -0
- lain/practicals/03_stateful_lifecycle.dart +65 -0
- lain/practicals/04_navigator_navigation.dart +60 -0
- lain/practicals/06_parent_child_state.dart +54 -0
- lain/practicals/09_login_form_validation.dart +73 -0
- lain/practicals/11_popup_menu.dart +40 -0
- lain/practicals/13_dropdown_dynamic_data.dart +47 -0
- lain/practicals/16_gridview.dart +44 -0
- lain/practicals/17_listview_custom_widgets.dart +47 -0
- lain/practicals/19_expanded_flexible.dart +74 -0
- lain/practicals/20_audio_playback.dart +71 -0
- lain/practicals/21_gesture_detector.dart +60 -0
- lain/practicals/__init__.py +0 -0
- lain_the_slayer-0.2.0.dist-info/METADATA +100 -0
- lain_the_slayer-0.2.0.dist-info/RECORD +21 -0
- lain_the_slayer-0.2.0.dist-info/WHEEL +5 -0
- lain_the_slayer-0.2.0.dist-info/entry_points.txt +2 -0
- lain_the_slayer-0.2.0.dist-info/licenses/LICENSE +17 -0
- lain_the_slayer-0.2.0.dist-info/top_level.txt +1 -0
lain/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
lain/cli.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from importlib.resources import files as resource_files
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
PRACTICALS = {
|
|
9
|
+
1: ("Hello World & Flutter Setup", "01_hello_world.dart"),
|
|
10
|
+
3: ("StatefulWidget Lifecycle", "03_stateful_lifecycle.dart"),
|
|
11
|
+
4: ("Navigator.push() and Navigator.pop()", "04_navigator_navigation.dart"),
|
|
12
|
+
6: ("Parent-Child State Management", "06_parent_child_state.dart"),
|
|
13
|
+
9: ("Login Form & Validation", "09_login_form_validation.dart"),
|
|
14
|
+
11: ("Create and Use a Menu", "11_popup_menu.dart"),
|
|
15
|
+
13: ("DropdownButton - Dynamic Data", "13_dropdown_dynamic_data.dart"),
|
|
16
|
+
16: ("GridView - Grid Layout", "16_gridview.dart"),
|
|
17
|
+
17: ("ListView - Custom Widgets", "17_listview_custom_widgets.dart"),
|
|
18
|
+
19: ("Expanded & Flexible", "19_expanded_flexible.dart"),
|
|
19
|
+
20: ("Audio Media Playback", "20_audio_playback.dart"),
|
|
20
|
+
21: ("GestureDetector & Button Callbacks", "21_gesture_detector.dart"),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def print_menu() -> None:
|
|
25
|
+
print("\n" + "=" * 56)
|
|
26
|
+
print(" LAIN - FLUTTER PRACTICALS")
|
|
27
|
+
print("=" * 56)
|
|
28
|
+
print("Select a practical:\n")
|
|
29
|
+
for number, (title, _) in PRACTICALS.items():
|
|
30
|
+
print(f" [{number:2}] {title}")
|
|
31
|
+
print("\n [ 0] Exit")
|
|
32
|
+
print("=" * 56)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_source(filename: str) -> str:
|
|
36
|
+
return (
|
|
37
|
+
resource_files("lain")
|
|
38
|
+
.joinpath("practicals")
|
|
39
|
+
.joinpath(filename)
|
|
40
|
+
.read_text(encoding="utf-8")
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def write_source(number: int, output: Path, flutter: bool) -> Path:
|
|
45
|
+
title, filename = PRACTICALS[number]
|
|
46
|
+
destination = output / "lib" / "main.dart" if flutter else output / f"practical_{number:02}.dart"
|
|
47
|
+
|
|
48
|
+
if flutter:
|
|
49
|
+
if not (output / "pubspec.yaml").is_file() or not (output / "lib").is_dir():
|
|
50
|
+
raise ValueError("--flutter requires the current directory to be a Flutter project.")
|
|
51
|
+
if destination.exists():
|
|
52
|
+
answer = input("lib/main.dart already exists. Overwrite it? [y/N]: ").strip().lower()
|
|
53
|
+
if answer not in {"y", "yes"}:
|
|
54
|
+
print("Cancelled; existing file was not changed.")
|
|
55
|
+
return destination
|
|
56
|
+
|
|
57
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
destination.write_text(get_source(filename), encoding="utf-8")
|
|
59
|
+
print(f"\n[+] Practical {number} selected: {title}")
|
|
60
|
+
print("[+] Dart source generated successfully.")
|
|
61
|
+
print(f"[+] Location: {destination.resolve()}")
|
|
62
|
+
|
|
63
|
+
if number == 20:
|
|
64
|
+
print("\n[!] Practical 20 requires setup:")
|
|
65
|
+
print(" Add audioplayers to pubspec.yaml, add assets/audio/music.mp3,")
|
|
66
|
+
print(" register that asset, then run: flutter pub get")
|
|
67
|
+
return destination
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main() -> None:
|
|
71
|
+
parser = argparse.ArgumentParser(
|
|
72
|
+
prog="lain",
|
|
73
|
+
description="Generate Dart source files for the 11 highlighted Flutter practicals.",
|
|
74
|
+
)
|
|
75
|
+
parser.add_argument("--list", action="store_true", help="List all available practicals.")
|
|
76
|
+
parser.add_argument("--show", type=int, metavar="NUMBER", help="Print a practical's Dart source.")
|
|
77
|
+
parser.add_argument("--flutter", action="store_true", help="Write selected code to lib/main.dart in the current Flutter project.")
|
|
78
|
+
parser.add_argument("--output", type=Path, default=Path.cwd(), help="Output directory (default: current directory).")
|
|
79
|
+
args = parser.parse_args()
|
|
80
|
+
|
|
81
|
+
if args.list:
|
|
82
|
+
print_menu()
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
if args.show is not None:
|
|
86
|
+
if args.show not in PRACTICALS:
|
|
87
|
+
parser.error(f"Practical {args.show} is not available.")
|
|
88
|
+
print(get_source(PRACTICALS[args.show][1]))
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
if args.flutter and args.output == Path.cwd():
|
|
92
|
+
# The current directory is intentionally used as the Flutter project root.
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
while True:
|
|
96
|
+
print_menu()
|
|
97
|
+
raw = input("Enter practical number: ").strip()
|
|
98
|
+
if raw == "0":
|
|
99
|
+
print("Good luck with your practical exam!")
|
|
100
|
+
return
|
|
101
|
+
try:
|
|
102
|
+
number = int(raw)
|
|
103
|
+
except ValueError:
|
|
104
|
+
print("Please enter a valid practical number.")
|
|
105
|
+
continue
|
|
106
|
+
if number not in PRACTICALS:
|
|
107
|
+
print("That number is not in the exam list. Choose one of the listed numbers.")
|
|
108
|
+
continue
|
|
109
|
+
try:
|
|
110
|
+
write_source(number, args.output, args.flutter)
|
|
111
|
+
except (OSError, ValueError) as error:
|
|
112
|
+
print(f"[!] {error}")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() {
|
|
4
|
+
runApp(const MyApp());
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class MyApp extends StatelessWidget {
|
|
8
|
+
const MyApp({super.key});
|
|
9
|
+
|
|
10
|
+
@override
|
|
11
|
+
Widget build(BuildContext context) {
|
|
12
|
+
return const MaterialApp(
|
|
13
|
+
debugShowCheckedModeBanner: false,
|
|
14
|
+
home: Scaffold(
|
|
15
|
+
appBar: AppBar(title: Text('Hello World')),
|
|
16
|
+
body: Center(
|
|
17
|
+
child: Text(
|
|
18
|
+
'Hello World',
|
|
19
|
+
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
|
20
|
+
),
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() => runApp(const MaterialApp(
|
|
4
|
+
debugShowCheckedModeBanner: false,
|
|
5
|
+
home: LifecycleDemo(),
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
class LifecycleDemo extends StatefulWidget {
|
|
9
|
+
const LifecycleDemo({super.key});
|
|
10
|
+
|
|
11
|
+
@override
|
|
12
|
+
State<LifecycleDemo> createState() {
|
|
13
|
+
debugPrint('createState() called');
|
|
14
|
+
return _LifecycleDemoState();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class _LifecycleDemoState extends State<LifecycleDemo> {
|
|
19
|
+
int count = 0;
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
void initState() {
|
|
23
|
+
super.initState();
|
|
24
|
+
debugPrint('initState() called');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
void didChangeDependencies() {
|
|
29
|
+
super.didChangeDependencies();
|
|
30
|
+
debugPrint('didChangeDependencies() called');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@override
|
|
34
|
+
Widget build(BuildContext context) {
|
|
35
|
+
debugPrint('build() called');
|
|
36
|
+
return Scaffold(
|
|
37
|
+
appBar: AppBar(title: const Text('StatefulWidget Lifecycle')),
|
|
38
|
+
body: Center(
|
|
39
|
+
child: Column(
|
|
40
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
41
|
+
children: [
|
|
42
|
+
Text('Counter: $count', style: const TextStyle(fontSize: 28)),
|
|
43
|
+
const SizedBox(height: 16),
|
|
44
|
+
ElevatedButton(
|
|
45
|
+
onPressed: () => setState(() => count++),
|
|
46
|
+
child: const Text('Increment'),
|
|
47
|
+
),
|
|
48
|
+
],
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
void deactivate() {
|
|
56
|
+
debugPrint('deactivate() called');
|
|
57
|
+
super.deactivate();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@override
|
|
61
|
+
void dispose() {
|
|
62
|
+
debugPrint('dispose() called');
|
|
63
|
+
super.dispose();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() {
|
|
4
|
+
runApp(const MyApp());
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class MyApp extends StatelessWidget {
|
|
8
|
+
const MyApp({super.key});
|
|
9
|
+
|
|
10
|
+
@override
|
|
11
|
+
Widget build(BuildContext context) {
|
|
12
|
+
return const MaterialApp(
|
|
13
|
+
debugShowCheckedModeBanner: false,
|
|
14
|
+
home: FirstScreen(),
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class FirstScreen extends StatelessWidget {
|
|
20
|
+
const FirstScreen({super.key});
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
Widget build(BuildContext context) {
|
|
24
|
+
return Scaffold(
|
|
25
|
+
appBar: AppBar(title: const Text('First Screen')),
|
|
26
|
+
body: Center(
|
|
27
|
+
child: ElevatedButton(
|
|
28
|
+
child: const Text('Go to Next Screen'),
|
|
29
|
+
onPressed: () {
|
|
30
|
+
Navigator.push(
|
|
31
|
+
context,
|
|
32
|
+
MaterialPageRoute(
|
|
33
|
+
builder: (context) => const SecondScreen(),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class SecondScreen extends StatelessWidget {
|
|
44
|
+
const SecondScreen({super.key});
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
Widget build(BuildContext context) {
|
|
48
|
+
return Scaffold(
|
|
49
|
+
appBar: AppBar(title: const Text('Second Screen')),
|
|
50
|
+
body: Center(
|
|
51
|
+
child: ElevatedButton(
|
|
52
|
+
child: const Text('Back to First Screen'),
|
|
53
|
+
onPressed: () {
|
|
54
|
+
Navigator.pop(context);
|
|
55
|
+
},
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() => runApp(const MaterialApp(
|
|
4
|
+
debugShowCheckedModeBanner: false,
|
|
5
|
+
home: ParentScreen(),
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
class ParentScreen extends StatefulWidget {
|
|
9
|
+
const ParentScreen({super.key});
|
|
10
|
+
|
|
11
|
+
@override
|
|
12
|
+
State<ParentScreen> createState() => _ParentScreenState();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class _ParentScreenState extends State<ParentScreen> {
|
|
16
|
+
int count = 0;
|
|
17
|
+
|
|
18
|
+
void increase() {
|
|
19
|
+
setState(() => count++);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
Widget build(BuildContext context) {
|
|
24
|
+
return Scaffold(
|
|
25
|
+
appBar: AppBar(title: const Text('Parent Child Example')),
|
|
26
|
+
body: Center(
|
|
27
|
+
child: ChildScreen(number: count, onPressed: increase),
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class ChildScreen extends StatelessWidget {
|
|
34
|
+
final int number;
|
|
35
|
+
final VoidCallback onPressed;
|
|
36
|
+
|
|
37
|
+
const ChildScreen({
|
|
38
|
+
super.key,
|
|
39
|
+
required this.number,
|
|
40
|
+
required this.onPressed,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
Widget build(BuildContext context) {
|
|
45
|
+
return Column(
|
|
46
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
47
|
+
children: [
|
|
48
|
+
Text('Count = $number', style: const TextStyle(fontSize: 25)),
|
|
49
|
+
const SizedBox(height: 20),
|
|
50
|
+
ElevatedButton(onPressed: onPressed, child: const Text('Increase')),
|
|
51
|
+
],
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() => runApp(const MaterialApp(
|
|
4
|
+
debugShowCheckedModeBanner: false,
|
|
5
|
+
home: LoginPage(),
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
class LoginPage extends StatefulWidget {
|
|
9
|
+
const LoginPage({super.key});
|
|
10
|
+
|
|
11
|
+
@override
|
|
12
|
+
State<LoginPage> createState() => _LoginPageState();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class _LoginPageState extends State<LoginPage> {
|
|
16
|
+
final formKey = GlobalKey<FormState>();
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
Widget build(BuildContext context) {
|
|
20
|
+
return Scaffold(
|
|
21
|
+
appBar: AppBar(title: const Text('Login Form')),
|
|
22
|
+
body: Padding(
|
|
23
|
+
padding: const EdgeInsets.all(20),
|
|
24
|
+
child: Form(
|
|
25
|
+
key: formKey,
|
|
26
|
+
child: Column(
|
|
27
|
+
children: [
|
|
28
|
+
TextFormField(
|
|
29
|
+
decoration: const InputDecoration(
|
|
30
|
+
labelText: 'Email',
|
|
31
|
+
border: OutlineInputBorder(),
|
|
32
|
+
),
|
|
33
|
+
validator: (value) {
|
|
34
|
+
if (value == null || value.isEmpty) {
|
|
35
|
+
return 'Enter Email';
|
|
36
|
+
}
|
|
37
|
+
if (!value.contains('@')) return 'Enter a valid email';
|
|
38
|
+
return null;
|
|
39
|
+
},
|
|
40
|
+
),
|
|
41
|
+
const SizedBox(height: 15),
|
|
42
|
+
TextFormField(
|
|
43
|
+
obscureText: true,
|
|
44
|
+
decoration: const InputDecoration(
|
|
45
|
+
labelText: 'Password',
|
|
46
|
+
border: OutlineInputBorder(),
|
|
47
|
+
),
|
|
48
|
+
validator: (value) {
|
|
49
|
+
if (value == null || value.isEmpty) {
|
|
50
|
+
return 'Enter Password';
|
|
51
|
+
}
|
|
52
|
+
if (value.length < 6) return 'Minimum 6 characters';
|
|
53
|
+
return null;
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
const SizedBox(height: 15),
|
|
57
|
+
ElevatedButton(
|
|
58
|
+
onPressed: () {
|
|
59
|
+
if (formKey.currentState!.validate()) {
|
|
60
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
61
|
+
const SnackBar(content: Text('Login Successful')),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
child: const Text('LOGIN'),
|
|
66
|
+
),
|
|
67
|
+
],
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() => runApp(const MaterialApp(
|
|
4
|
+
debugShowCheckedModeBanner: false,
|
|
5
|
+
home: MenuDemo(),
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
class MenuDemo extends StatefulWidget {
|
|
9
|
+
const MenuDemo({super.key});
|
|
10
|
+
|
|
11
|
+
@override
|
|
12
|
+
State<MenuDemo> createState() => _MenuDemoState();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class _MenuDemoState extends State<MenuDemo> {
|
|
16
|
+
String selected = 'Nothing selected';
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
Widget build(BuildContext context) {
|
|
20
|
+
return Scaffold(
|
|
21
|
+
appBar: AppBar(
|
|
22
|
+
title: const Text('Menu Example'),
|
|
23
|
+
actions: [
|
|
24
|
+
PopupMenuButton<String>(
|
|
25
|
+
onSelected: (value) => setState(() => selected = value),
|
|
26
|
+
itemBuilder: (context) => const [
|
|
27
|
+
PopupMenuItem(value: 'Home', child: Text('Home')),
|
|
28
|
+
PopupMenuItem(value: 'About', child: Text('About')),
|
|
29
|
+
PopupMenuItem(value: 'Exit', child: Text('Exit')),
|
|
30
|
+
],
|
|
31
|
+
),
|
|
32
|
+
],
|
|
33
|
+
),
|
|
34
|
+
body: Center(
|
|
35
|
+
child: Text('Selected: $selected',
|
|
36
|
+
style: const TextStyle(fontSize: 24)),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() => runApp(const MaterialApp(
|
|
4
|
+
debugShowCheckedModeBanner: false,
|
|
5
|
+
home: DropdownDemo(),
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
class DropdownDemo extends StatefulWidget {
|
|
9
|
+
const DropdownDemo({super.key});
|
|
10
|
+
|
|
11
|
+
@override
|
|
12
|
+
State<DropdownDemo> createState() => _DropdownDemoState();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class _DropdownDemoState extends State<DropdownDemo> {
|
|
16
|
+
final List<String> items = ['Apple', 'Banana', 'Mango', 'Orange'];
|
|
17
|
+
String selected = 'Apple';
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
Widget build(BuildContext context) {
|
|
21
|
+
return Scaffold(
|
|
22
|
+
appBar: AppBar(title: const Text('Dropdown Example')),
|
|
23
|
+
body: Center(
|
|
24
|
+
child: Column(
|
|
25
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
26
|
+
children: [
|
|
27
|
+
DropdownButton<String>(
|
|
28
|
+
value: selected,
|
|
29
|
+
items: items.map((item) {
|
|
30
|
+
return DropdownMenuItem<String>(
|
|
31
|
+
value: item,
|
|
32
|
+
child: Text(item),
|
|
33
|
+
);
|
|
34
|
+
}).toList(),
|
|
35
|
+
onChanged: (value) {
|
|
36
|
+
if (value != null) setState(() => selected = value);
|
|
37
|
+
},
|
|
38
|
+
),
|
|
39
|
+
const SizedBox(height: 20),
|
|
40
|
+
Text('Selected: $selected',
|
|
41
|
+
style: const TextStyle(fontSize: 20)),
|
|
42
|
+
],
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() => runApp(const MaterialApp(
|
|
4
|
+
debugShowCheckedModeBanner: false,
|
|
5
|
+
home: GridDemo(),
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
class GridDemo extends StatelessWidget {
|
|
9
|
+
const GridDemo({super.key});
|
|
10
|
+
|
|
11
|
+
final List<String> items = const [
|
|
12
|
+
'Apple', 'Banana', 'Mango', 'Orange', 'Grapes', 'Pineapple'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
@override
|
|
16
|
+
Widget build(BuildContext context) {
|
|
17
|
+
return Scaffold(
|
|
18
|
+
appBar: AppBar(title: const Text('GridView Example')),
|
|
19
|
+
body: Padding(
|
|
20
|
+
padding: const EdgeInsets.all(20),
|
|
21
|
+
child: GridView.builder(
|
|
22
|
+
itemCount: items.length,
|
|
23
|
+
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
24
|
+
crossAxisCount: 2,
|
|
25
|
+
crossAxisSpacing: 10,
|
|
26
|
+
mainAxisSpacing: 10,
|
|
27
|
+
childAspectRatio: 2.5,
|
|
28
|
+
),
|
|
29
|
+
itemBuilder: (context, index) {
|
|
30
|
+
return Container(
|
|
31
|
+
color: Colors.blue,
|
|
32
|
+
child: Center(
|
|
33
|
+
child: Text(
|
|
34
|
+
items[index],
|
|
35
|
+
style: const TextStyle(color: Colors.white, fontSize: 18),
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
},
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() => runApp(const MaterialApp(
|
|
4
|
+
debugShowCheckedModeBanner: false,
|
|
5
|
+
home: StudentListPage(),
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
class StudentListPage extends StatelessWidget {
|
|
9
|
+
const StudentListPage({super.key});
|
|
10
|
+
|
|
11
|
+
final List<String> students = const [
|
|
12
|
+
'Rahul', 'Priya', 'Amit', 'Sneha', 'Arjun'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
@override
|
|
16
|
+
Widget build(BuildContext context) {
|
|
17
|
+
return Scaffold(
|
|
18
|
+
appBar: AppBar(title: const Text('Student List')),
|
|
19
|
+
body: ListView.builder(
|
|
20
|
+
itemCount: students.length,
|
|
21
|
+
itemBuilder: (context, index) {
|
|
22
|
+
return StudentTile(name: students[index], number: index + 1);
|
|
23
|
+
},
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class StudentTile extends StatelessWidget {
|
|
30
|
+
final String name;
|
|
31
|
+
final int number;
|
|
32
|
+
|
|
33
|
+
const StudentTile({super.key, required this.name, required this.number});
|
|
34
|
+
|
|
35
|
+
@override
|
|
36
|
+
Widget build(BuildContext context) {
|
|
37
|
+
return Card(
|
|
38
|
+
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
39
|
+
child: ListTile(
|
|
40
|
+
leading: CircleAvatar(child: Text('$number')),
|
|
41
|
+
title: Text(name),
|
|
42
|
+
subtitle: const Text('Computer Science Student'),
|
|
43
|
+
trailing: const Icon(Icons.arrow_forward_ios),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() => runApp(const MaterialApp(
|
|
4
|
+
debugShowCheckedModeBanner: false,
|
|
5
|
+
home: ResponsiveDemo(),
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
class ResponsiveDemo extends StatelessWidget {
|
|
9
|
+
const ResponsiveDemo({super.key});
|
|
10
|
+
|
|
11
|
+
@override
|
|
12
|
+
Widget build(BuildContext context) {
|
|
13
|
+
return Scaffold(
|
|
14
|
+
appBar: AppBar(title: const Text('Expanded and Flexible')),
|
|
15
|
+
body: Padding(
|
|
16
|
+
padding: const EdgeInsets.all(16),
|
|
17
|
+
child: Column(
|
|
18
|
+
children: [
|
|
19
|
+
const Text('Expanded widgets', style: TextStyle(fontSize: 20)),
|
|
20
|
+
const SizedBox(height: 10),
|
|
21
|
+
Row(
|
|
22
|
+
children: [
|
|
23
|
+
Expanded(
|
|
24
|
+
flex: 2,
|
|
25
|
+
child: Container(
|
|
26
|
+
height: 80,
|
|
27
|
+
color: Colors.blue,
|
|
28
|
+
child: const Center(
|
|
29
|
+
child: Text('Expanded 2',
|
|
30
|
+
style: TextStyle(color: Colors.white)),
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
const SizedBox(width: 8),
|
|
35
|
+
Expanded(
|
|
36
|
+
flex: 1,
|
|
37
|
+
child: Container(
|
|
38
|
+
height: 80,
|
|
39
|
+
color: Colors.orange,
|
|
40
|
+
child: const Center(child: Text('Expanded 1')),
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
],
|
|
44
|
+
),
|
|
45
|
+
const SizedBox(height: 30),
|
|
46
|
+
const Text('Flexible widget', style: TextStyle(fontSize: 20)),
|
|
47
|
+
const SizedBox(height: 10),
|
|
48
|
+
Row(
|
|
49
|
+
children: [
|
|
50
|
+
Flexible(
|
|
51
|
+
child: Container(
|
|
52
|
+
height: 80,
|
|
53
|
+
color: Colors.green,
|
|
54
|
+
child: const Center(child: Text('Flexible')),
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
const SizedBox(width: 8),
|
|
58
|
+
Container(
|
|
59
|
+
width: 100,
|
|
60
|
+
height: 80,
|
|
61
|
+
color: Colors.purple,
|
|
62
|
+
child: const Center(
|
|
63
|
+
child: Text('Fixed width',
|
|
64
|
+
style: TextStyle(color: Colors.white)),
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
],
|
|
68
|
+
),
|
|
69
|
+
],
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Practical 20: Audio playback using the audioplayers plugin.
|
|
2
|
+
//
|
|
3
|
+
// Before running:
|
|
4
|
+
// 1. Add audioplayers to pubspec.yaml:
|
|
5
|
+
// dependencies:
|
|
6
|
+
// flutter:
|
|
7
|
+
// sdk: flutter
|
|
8
|
+
// audioplayers: ^6.0.0
|
|
9
|
+
// 2. Put an audio file at assets/audio/music.mp3
|
|
10
|
+
// 3. Register it in pubspec.yaml:
|
|
11
|
+
// flutter:
|
|
12
|
+
// assets:
|
|
13
|
+
// - assets/audio/music.mp3
|
|
14
|
+
// 4. Run: flutter pub get
|
|
15
|
+
|
|
16
|
+
import 'package:flutter/material.dart';
|
|
17
|
+
import 'package:audioplayers/audioplayers.dart';
|
|
18
|
+
|
|
19
|
+
void main() => runApp(const MaterialApp(
|
|
20
|
+
debugShowCheckedModeBanner: false,
|
|
21
|
+
home: AudioDemo(),
|
|
22
|
+
));
|
|
23
|
+
|
|
24
|
+
class AudioDemo extends StatefulWidget {
|
|
25
|
+
const AudioDemo({super.key});
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
State<AudioDemo> createState() => _AudioDemoState();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class _AudioDemoState extends State<AudioDemo> {
|
|
32
|
+
final AudioPlayer player = AudioPlayer();
|
|
33
|
+
bool isPlaying = false;
|
|
34
|
+
|
|
35
|
+
Future<void> playAudio() async {
|
|
36
|
+
await player.play(AssetSource('audio/music.mp3'));
|
|
37
|
+
setState(() => isPlaying = true);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Future<void> pauseAudio() async {
|
|
41
|
+
await player.pause();
|
|
42
|
+
setState(() => isPlaying = false);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@override
|
|
46
|
+
void dispose() {
|
|
47
|
+
player.dispose();
|
|
48
|
+
super.dispose();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@override
|
|
52
|
+
Widget build(BuildContext context) {
|
|
53
|
+
return Scaffold(
|
|
54
|
+
appBar: AppBar(title: const Text('Audio Playback')),
|
|
55
|
+
body: Center(
|
|
56
|
+
child: Column(
|
|
57
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
58
|
+
children: [
|
|
59
|
+
const Icon(Icons.music_note, size: 80),
|
|
60
|
+
const SizedBox(height: 20),
|
|
61
|
+
ElevatedButton.icon(
|
|
62
|
+
onPressed: isPlaying ? pauseAudio : playAudio,
|
|
63
|
+
icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow),
|
|
64
|
+
label: Text(isPlaying ? 'Pause Audio' : 'Play Audio'),
|
|
65
|
+
),
|
|
66
|
+
],
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() => runApp(const MaterialApp(
|
|
4
|
+
debugShowCheckedModeBanner: false,
|
|
5
|
+
home: GestureDemo(),
|
|
6
|
+
));
|
|
7
|
+
|
|
8
|
+
class GestureDemo extends StatefulWidget {
|
|
9
|
+
const GestureDemo({super.key});
|
|
10
|
+
|
|
11
|
+
@override
|
|
12
|
+
State<GestureDemo> createState() => _GestureDemoState();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class _GestureDemoState extends State<GestureDemo> {
|
|
16
|
+
String message = 'Perform a gesture';
|
|
17
|
+
|
|
18
|
+
void updateMessage(String value) {
|
|
19
|
+
setState(() => message = value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
Widget build(BuildContext context) {
|
|
24
|
+
return Scaffold(
|
|
25
|
+
appBar: AppBar(title: const Text('Gesture Detector Demo')),
|
|
26
|
+
body: Center(
|
|
27
|
+
child: Column(
|
|
28
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
29
|
+
children: [
|
|
30
|
+
GestureDetector(
|
|
31
|
+
onTap: () => updateMessage('Single Tap Detected'),
|
|
32
|
+
onDoubleTap: () => updateMessage('Double Tap Detected'),
|
|
33
|
+
onLongPress: () => updateMessage('Long Press Detected'),
|
|
34
|
+
child: Container(
|
|
35
|
+
padding: const EdgeInsets.all(30),
|
|
36
|
+
color: Colors.blue,
|
|
37
|
+
child: const Text(
|
|
38
|
+
'Tap / Double Tap / Long Press',
|
|
39
|
+
style: TextStyle(color: Colors.white),
|
|
40
|
+
textAlign: TextAlign.center,
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
const SizedBox(height: 20),
|
|
45
|
+
Text(message, style: const TextStyle(fontSize: 20)),
|
|
46
|
+
const SizedBox(height: 20),
|
|
47
|
+
ElevatedButton(
|
|
48
|
+
onPressed: () => updateMessage('ElevatedButton Pressed'),
|
|
49
|
+
child: const Text('Elevated Button'),
|
|
50
|
+
),
|
|
51
|
+
TextButton(
|
|
52
|
+
onPressed: () => updateMessage('TextButton Pressed'),
|
|
53
|
+
child: const Text('Text Button'),
|
|
54
|
+
),
|
|
55
|
+
],
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lain-the-slayer
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A CLI tool that generates Dart files for 11 highlighted Flutter practicals.
|
|
5
|
+
Author: Lain
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: flutter,dart,practical,education,cli
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Topic :: Education
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Lain — Flutter Practicals CLI
|
|
18
|
+
|
|
19
|
+
Lain is a small Python command-line tool that generates Dart source files for the 11 highlighted Flutter practicals.
|
|
20
|
+
|
|
21
|
+
## Included practicals
|
|
22
|
+
|
|
23
|
+
1. Hello World & Flutter setup
|
|
24
|
+
3. StatefulWidget lifecycle
|
|
25
|
+
4. Navigator.push() and Navigator.pop()
|
|
26
|
+
6. Parent-child state management
|
|
27
|
+
9. Login form and validation
|
|
28
|
+
11. Popup menu
|
|
29
|
+
13. DropdownButton with dynamic data
|
|
30
|
+
16. GridView
|
|
31
|
+
17. ListView with custom widgets
|
|
32
|
+
19. Expanded and Flexible
|
|
33
|
+
20. Audio playback
|
|
34
|
+
21. GestureDetector and button callbacks
|
|
35
|
+
|
|
36
|
+
## Install locally
|
|
37
|
+
|
|
38
|
+
From this project directory:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m pip install .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or install in editable/development mode:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -m pip install -e .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Run
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
lain
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Choose a practical number. Lain writes a Dart file into the current working directory, for example `practical_16.dart`.
|
|
57
|
+
|
|
58
|
+
## Other commands
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
lain --list
|
|
62
|
+
lain --show 16
|
|
63
|
+
lain --output ./generated
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
To write the selected practical directly into an existing Flutter project's `lib/main.dart`, run `lain --flutter` from the Flutter project root. Lain checks for `pubspec.yaml` and asks before overwriting `lib/main.dart`.
|
|
67
|
+
|
|
68
|
+
## Practical 20 setup
|
|
69
|
+
|
|
70
|
+
The audio practical uses the `audioplayers` Flutter plugin and an audio asset. Add this to the Flutter project's `pubspec.yaml`:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
dependencies:
|
|
74
|
+
flutter:
|
|
75
|
+
sdk: flutter
|
|
76
|
+
audioplayers: ^6.0.0
|
|
77
|
+
|
|
78
|
+
flutter:
|
|
79
|
+
assets:
|
|
80
|
+
- assets/audio/music.mp3
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Place an audio file at `assets/audio/music.mp3`, then run:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
flutter pub get
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The generated Dart file is named `practical_20.dart`. For plugin-based practicals, additional Flutter dependencies and assets may be required; see comments in the generated file.
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python -m pip install -e .
|
|
95
|
+
lain --list
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
lain/__init__.py,sha256=Zn1KFblwuFHiDRdRAiRnDBRkbPttWh44jKa5zG2ov0E,22
|
|
2
|
+
lain/cli.py,sha256=dk8hSWcKhg1USihVHIEkVvrfE2ln9atM7LDNPza5zsY,4471
|
|
3
|
+
lain/practicals/01_hello_world.dart,sha256=svu4jiI5bP7L027M6ss9dlqe5uFUTxZ50fi1hsd81D0,537
|
|
4
|
+
lain/practicals/03_stateful_lifecycle.dart,sha256=gToMY-9NwV6suQpDnIFAW3xKzrXRDN5jKYi2tykfOgU,1480
|
|
5
|
+
lain/practicals/04_navigator_navigation.dart,sha256=KQfubCDxG95PnNIFIrWRovIc7c7jQqHdQSGSC3sw-5I,1294
|
|
6
|
+
lain/practicals/06_parent_child_state.dart,sha256=VAQ_vhnbkKFMMfbJvJej8TWhpCe6c-hmN4lXNkiHG88,1228
|
|
7
|
+
lain/practicals/09_login_form_validation.dart,sha256=TcxmZfgvwhK2XEGtO8gEv1oeSx6tDXtmAXgysRYWVlQ,2203
|
|
8
|
+
lain/practicals/11_popup_menu.dart,sha256=8ySenhvsIjs7hNmlCcGRDRIWgMXx7AOg0rSCL6S0u9s,1071
|
|
9
|
+
lain/practicals/13_dropdown_dynamic_data.dart,sha256=bJ177p-YFYM31ksBZztx-GJhxgbcSbS6rlKM8MzBsd4,1310
|
|
10
|
+
lain/practicals/16_gridview.dart,sha256=YEY8uvaL7iDncrsh5pSZjv8QDlmjRRk2HiLnsEaTs4M,1190
|
|
11
|
+
lain/practicals/17_listview_custom_widgets.dart,sha256=hc1Ttjz_DzkryAGGwHAWeO3ojpTI0vXN_gh1VXg1YMs,1226
|
|
12
|
+
lain/practicals/19_expanded_flexible.dart,sha256=5i2lRPblvMc5b9duykiiJV8uvk0u4ph0apiCOM_XgTc,2253
|
|
13
|
+
lain/practicals/20_audio_playback.dart,sha256=PXSDltqT-VbrlbrAD3yzN4TCOvQmxw6-8Ke9aXgD4dk,1832
|
|
14
|
+
lain/practicals/21_gesture_detector.dart,sha256=aP4uo0itzokmgKthZIndceIa7uQt2R-IhXIS20of_O8,1851
|
|
15
|
+
lain/practicals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
lain_the_slayer-0.2.0.dist-info/licenses/LICENSE,sha256=6WntgUGFd-PADyvZl4NrrnYdACTL2LrBPAd2PQHqF3A,810
|
|
17
|
+
lain_the_slayer-0.2.0.dist-info/METADATA,sha256=NI1QFSWTZQl_EHAJH1nO2DnWuo_5JYNt7UCKF22KXwE,2274
|
|
18
|
+
lain_the_slayer-0.2.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
19
|
+
lain_the_slayer-0.2.0.dist-info/entry_points.txt,sha256=tJdUUZk-NcSPeIAFwexjF6NDF55tsGNesrLUbKO_8dY,39
|
|
20
|
+
lain_the_slayer-0.2.0.dist-info/top_level.txt,sha256=bH3V4uoUY2hyNhrsMc2swOHscjBrkIMgLhWYKv0WZNM,5
|
|
21
|
+
lain_the_slayer-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Owais Idrisi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lain
|