Skip to content

Commit cda9b25

Browse files
committed
feat: navigate live channels on live stream screen or player
1 parent d2ffd9c commit cda9b25

File tree

14 files changed

+622
-263
lines changed

14 files changed

+622
-263
lines changed

lib/database/database.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,17 @@ class AppDatabase extends _$AppDatabase {
10241024
return vodStreamData != null ? VodStream.fromDriftVodStream(vodStreamData) : null;
10251025
}
10261026

1027+
Future<LiveStream?> findLiveStreamById(String streamId, String playlistId) async {
1028+
var liveStreamData = await (select(liveStreams)..where(
1029+
(tbl) =>
1030+
tbl.playlistId.equals(playlistId) &
1031+
tbl.streamId.equals(streamId),
1032+
))
1033+
.getSingleOrNull();
1034+
1035+
return liveStreamData != null ? LiveStream.fromDriftLiveStream(liveStreamData) : null;
1036+
}
1037+
10271038
// Clear operations
10281039
Future<int> clearSeriesData(String seriesId, String playlistId) async {
10291040
await (delete(episodes)..where(

lib/main.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'package:flutter/material.dart';
2-
import 'package:iptv_player/database/database.dart';
32
import 'package:iptv_player/services/service_locator.dart';
43
import 'package:provider/provider.dart';
54
import 'database/playlist_controller.dart';

lib/utils/build_media_url.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:iptv_player/services/app_state.dart';
2+
3+
import '../models/content_type.dart';
4+
import '../models/playlist_content_model.dart';
5+
import '../models/playlist_model.dart';
6+
7+
String buildMediaUrl(ContentItem contentItem) {
8+
var playlist = AppState.currentPlaylist!;
9+
switch (contentItem.contentType) {
10+
case ContentType.liveStream:
11+
return '${playlist.url}/${playlist.username}/${playlist.password}/${contentItem.id}';
12+
case ContentType.vod:
13+
return '${playlist.url}/movie/${playlist.username}/${playlist.password}/${contentItem.id}.${contentItem.containerExtension!}';
14+
case ContentType.series:
15+
return '${playlist.url}/series/${playlist.username}/${playlist.password}/${contentItem.id}.${contentItem.containerExtension!}';
16+
}
17+
}
Lines changed: 186 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,203 @@
1+
import 'dart:async';
2+
13
import 'package:flutter/material.dart';
24
import 'package:iptv_player/models/playlist_content_model.dart';
35
import 'package:iptv_player/services/app_state.dart';
46
import 'package:iptv_player/views/widgets/player_widget.dart';
7+
import '../../../models/content_type.dart';
8+
import '../../../services/event_bus.dart';
9+
import '../../../utils/responsive_helper.dart';
10+
import '../../widgets/content_item_card_widget.dart';
511

6-
class LiveStreamScreen extends StatelessWidget {
12+
class LiveStreamScreen extends StatefulWidget {
713
final ContentItem content;
814

915
const LiveStreamScreen({super.key, required this.content});
1016

17+
@override
18+
State<LiveStreamScreen> createState() => _LiveStreamScreenState();
19+
}
20+
21+
class _LiveStreamScreenState extends State<LiveStreamScreen> {
22+
late ContentItem contentItem;
23+
List<ContentItem> allContents = [];
24+
bool allContentsLoaded = false;
25+
int selectedContentItemIndex = 0;
26+
late StreamSubscription contentItemIndexChangedSubscription;
27+
28+
@override
29+
void initState() {
30+
super.initState();
31+
contentItem = widget.content;
32+
_initializeCategory();
33+
}
34+
35+
Future<void> _initializeCategory() async {
36+
allContents =
37+
(await AppState.repository!.getLiveChannelsByCategoryId(
38+
categoryId: widget.content.liveStream!.categoryId,
39+
))!.map((x) {
40+
return ContentItem(
41+
x.streamId,
42+
x.name,
43+
x.streamIcon,
44+
ContentType.liveStream,
45+
liveStream: x,
46+
);
47+
}).toList();
48+
49+
setState(() {
50+
selectedContentItemIndex = allContents.indexWhere(
51+
(element) => element.id == widget.content.id,
52+
);
53+
allContentsLoaded = true;
54+
});
55+
56+
contentItemIndexChangedSubscription = EventBus()
57+
.on<int>('player_content_item_index')
58+
.listen((int index) {
59+
if (!mounted) return;
60+
61+
setState(() {
62+
selectedContentItemIndex = index;
63+
contentItem = allContents[selectedContentItemIndex];
64+
});
65+
});
66+
}
67+
68+
@override
69+
void dispose() {
70+
contentItemIndexChangedSubscription.cancel();
71+
super.dispose();
72+
}
73+
1174
@override
1275
Widget build(BuildContext context) {
1376
return Scaffold(
14-
// appBar: AppBar(),
1577
body: SingleChildScrollView(
1678
child: Column(
1779
crossAxisAlignment: CrossAxisAlignment.stretch,
1880
children: [
19-
Hero(
20-
tag: content.id,
21-
child: PlayerWidget(
22-
playlist: AppState.currentPlaylist!,
23-
contentItem: content,
24-
),
25-
),
26-
Container(
27-
decoration: BoxDecoration(
28-
gradient: LinearGradient(
29-
begin: Alignment.topCenter,
30-
end: Alignment.bottomCenter,
31-
colors: [
32-
Theme.of(context).scaffoldBackgroundColor.withOpacity(0.8),
33-
Theme.of(context).scaffoldBackgroundColor,
34-
],
35-
),
36-
),
37-
child: Padding(
38-
padding: const EdgeInsets.all(20.0),
39-
child: Column(
40-
crossAxisAlignment: CrossAxisAlignment.start,
41-
children: [
42-
// Kanal Başlığı
43-
Row(
44-
children: [
45-
Container(
46-
padding: const EdgeInsets.symmetric(
47-
horizontal: 12,
48-
vertical: 6,
49-
),
50-
decoration: BoxDecoration(
51-
color: Colors.red,
52-
borderRadius: BorderRadius.circular(20),
53-
),
54-
child: Row(
55-
mainAxisSize: MainAxisSize.min,
56-
children: [
57-
Container(
58-
width: 8,
59-
height: 8,
60-
decoration: const BoxDecoration(
61-
color: Colors.white,
62-
shape: BoxShape.circle,
63-
),
81+
// Hero(
82+
// tag: 'live_stream_'+widget.content.id,
83+
// child: PlayerWidget(contentItem: widget.content),
84+
// ),
85+
PlayerWidget(contentItem: widget.content),
86+
Padding(
87+
padding: const EdgeInsets.all(20.0),
88+
child: Column(
89+
crossAxisAlignment: CrossAxisAlignment.start,
90+
children: [
91+
// Kanal Başlığı
92+
Row(
93+
children: [
94+
Container(
95+
padding: const EdgeInsets.symmetric(
96+
horizontal: 12,
97+
vertical: 6,
98+
),
99+
decoration: BoxDecoration(
100+
color: Colors.red,
101+
borderRadius: BorderRadius.circular(20),
102+
),
103+
child: Row(
104+
mainAxisSize: MainAxisSize.min,
105+
children: [
106+
Container(
107+
width: 8,
108+
height: 8,
109+
decoration: const BoxDecoration(
110+
color: Colors.white,
111+
shape: BoxShape.circle,
64112
),
65-
const SizedBox(width: 6),
66-
const SelectableText(
67-
'CANLI',
68-
style: TextStyle(
69-
color: Colors.white,
70-
fontSize: 12,
71-
fontWeight: FontWeight.bold,
72-
),
113+
),
114+
const SizedBox(width: 6),
115+
const SelectableText(
116+
'CANLI',
117+
style: TextStyle(
118+
color: Colors.white,
119+
fontSize: 12,
120+
fontWeight: FontWeight.bold,
73121
),
74-
],
75-
),
122+
),
123+
],
76124
),
77-
const SizedBox(width: 16),
78-
Expanded(
79-
child: SelectableText(
80-
content.name,
81-
style: Theme.of(context).textTheme.headlineSmall
82-
?.copyWith(
83-
fontWeight: FontWeight.bold,
84-
),
85-
),
125+
),
126+
const SizedBox(width: 16),
127+
Expanded(
128+
child: SelectableText(
129+
contentItem.name,
130+
style: Theme.of(context).textTheme.headlineSmall
131+
?.copyWith(fontWeight: FontWeight.bold),
86132
),
87-
],
88-
),
89-
const SizedBox(height: 24),
90-
91-
// Kanal Bilgileri
92-
SelectableText(
93-
'Kanal Bilgileri',
94-
style: Theme.of(context).textTheme.titleLarge?.copyWith(
95-
fontWeight: FontWeight.bold,
96133
),
134+
],
135+
),
136+
const SizedBox(height: 24),
137+
SelectableText(
138+
'Diğer Kanallar',
139+
style: Theme.of(context).textTheme.titleLarge?.copyWith(
140+
fontWeight: FontWeight.bold,
97141
),
98-
const SizedBox(height: 16),
99-
100-
_buildInfoCard(
101-
icon: Icons.tv,
102-
title: 'Kanal ID',
103-
value: content.id.toString(),
104-
color: Colors.blue,
142+
),
143+
const SizedBox(height: 16),
144+
if (allContentsLoaded)
145+
ContentItemCardWidget(
146+
cardHeight: ResponsiveHelper.getCardHeight(context),
147+
cardWidth: ResponsiveHelper.getCardWidth(context),
148+
contentItems: allContents,
149+
onContentTap: _onContentTap,
150+
initialSelectedIndex: selectedContentItemIndex,
151+
isSelectionModeEnabled: true,
152+
heroEnabled: false,
105153
),
106-
const SizedBox(height: 12),
107154

108-
_buildInfoCard(
109-
icon: Icons.category,
110-
title: 'Kategori ID',
111-
value: content.liveStream?.categoryId ?? 'Belirtilmemiş',
112-
color: Colors.green,
113-
),
114-
const SizedBox(height: 12),
155+
const SizedBox(height: 24),
115156

116-
_buildInfoCard(
117-
icon: Icons.high_quality,
118-
title: 'Kalite',
119-
value: _getQualityText(),
120-
color: Colors.orange,
121-
),
122-
const SizedBox(height: 12),
123-
124-
_buildInfoCard(
125-
icon: Icons.signal_cellular_alt,
126-
title: 'Stream Türü',
127-
value:
128-
content.containerExtension?.toUpperCase() ??
129-
'Canlı Yayın',
130-
color: Colors.purple,
157+
// Kanal Bilgileri
158+
SelectableText(
159+
'Kanal Bilgileri',
160+
style: Theme.of(context).textTheme.titleLarge?.copyWith(
161+
fontWeight: FontWeight.bold,
131162
),
132-
const SizedBox(height: 24),
133-
],
134-
),
163+
),
164+
const SizedBox(height: 16),
165+
166+
_buildInfoCard(
167+
icon: Icons.tv,
168+
title: 'Kanal ID',
169+
value: contentItem.id.toString(),
170+
color: Colors.blue,
171+
),
172+
const SizedBox(height: 12),
173+
174+
_buildInfoCard(
175+
icon: Icons.category,
176+
title: 'Kategori ID',
177+
value:
178+
contentItem.liveStream?.categoryId ?? 'Belirtilmemiş',
179+
color: Colors.green,
180+
),
181+
const SizedBox(height: 12),
182+
183+
_buildInfoCard(
184+
icon: Icons.high_quality,
185+
title: 'Kalite',
186+
value: _getQualityText(),
187+
color: Colors.orange,
188+
),
189+
const SizedBox(height: 12),
190+
191+
_buildInfoCard(
192+
icon: Icons.signal_cellular_alt,
193+
title: 'Stream Türü',
194+
value:
195+
contentItem.containerExtension?.toUpperCase() ??
196+
'Canlı Yayın',
197+
color: Colors.purple,
198+
),
199+
const SizedBox(height: 24),
200+
],
135201
),
136202
),
137203
],
@@ -230,4 +296,16 @@ class LiveStreamScreen extends StatelessWidget {
230296
// Şu an için sabit bir değer döndürüyorum
231297
return 'HD';
232298
}
299+
300+
_onContentTap(ContentItem contentItem) {
301+
setState(() {
302+
if (!mounted) return;
303+
304+
selectedContentItemIndex = allContents.indexOf(contentItem);
305+
});
306+
EventBus().emit(
307+
'player_content_item_index_changed',
308+
selectedContentItemIndex,
309+
);
310+
}
233311
}

lib/views/screens/movies/movide_screen.dart

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ class _MovieScreenState extends State<MovieScreen> {
2020
child: Column(
2121
crossAxisAlignment: CrossAxisAlignment.stretch,
2222
children: [
23-
Hero(
24-
tag: widget.contentItem.id,
25-
child: PlayerWidget(
26-
playlist: AppState.currentPlaylist!,
27-
contentItem: widget.contentItem,
28-
),
23+
// Hero(
24+
// tag: 'movie_'+widget.contentItem.id,
25+
// child: PlayerWidget(
26+
// contentItem: widget.contentItem,
27+
// ),
28+
// ),
29+
PlayerWidget(
30+
contentItem: widget.contentItem,
2931
),
3032
Container(
3133
decoration: BoxDecoration(

0 commit comments

Comments
 (0)