Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/build-apk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Build APK

on:
push:
branches: [ main, copilot/** ]
pull_request:
branches: [ main ]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.19.0'
channel: 'stable'

- name: Get dependencies
run: flutter pub get

- name: Build APK
run: flutter build apk --release

- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: release-apk
path: build/app/outputs/flutter-apk/app-release.apk
retention-days: 30
106 changes: 106 additions & 0 deletions BUILD_INSTRUCTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Build and Test Instructions

## Building the APK

### Option 1: Using GitHub Actions (Recommended)

The repository now has an automated build workflow that generates APK files automatically.

**Steps:**
1. After pushing commits to this branch, go to the "Actions" tab in GitHub
2. Find the "Build APK" workflow run for your commit
3. Wait for the build to complete (usually 5-10 minutes)
4. Download the APK from the "Artifacts" section at the bottom of the workflow run
5. Transfer the APK to your Android device and install it

**Manual Trigger:**
You can also manually trigger the build workflow:
1. Go to the "Actions" tab
2. Select "Build APK" from the workflows list
3. Click "Run workflow" button
4. Select the branch (copilot/add-group-channels-and-numbers)
5. Click "Run workflow" to start the build

### Option 2: Build Locally

If you have Flutter installed on your machine:

```bash
# 1. Clone the repository
git clone https://github.com/BiplopDey/Indian-IPTV-App.git
cd Indian-IPTV-App

# 2. Checkout the branch
git checkout copilot/add-group-channels-and-numbers

# 3. Install dependencies
flutter pub get

# 4. Build the APK
flutter build apk --release

# 5. Find the APK at:
# build/app/outputs/flutter-apk/app-release.apk
```

## Testing the New Features

### 1. Channel Numbers
- Open the app and browse the channel list
- Each channel should display a number badge (1, 2, 3, etc.) before the logo
- In the player screen, the channel number should appear in the AppBar

### 2. Country Grouping
- On the home screen, tap the "Grouped View" button (top right)
- Channels should now be organized by country
- Each country section shows the flag icon, country name, and channel count
- Tap on a country to expand/collapse the channel list

### 3. Country Filtering
- Scroll the horizontal chip row below the search bar
- Tap on a country chip to filter channels by that country
- Tap "All" to show all channels again

### 4. TV Remote Navigation
- Connect a keyboard to your Android TV or use an Android emulator with keyboard
- Type a channel number (e.g., "5", "23", "100")
- A large number overlay appears in the top-right corner showing your input
- After 1.5 seconds of no input, the app automatically navigates to that channel
- If the channel doesn't exist, an error message appears

### Testing Tips

**For TV Remote/Keyboard Navigation:**
- Use Android TV or an emulator with keyboard support
- Both regular number keys (0-9) and numpad keys work
- Test multi-digit numbers (e.g., 25, 123)
- Try invalid numbers to test error handling

**For Grouped View:**
- Test with channels from multiple countries
- Verify channel counts are correct
- Test expanding/collapsing multiple countries

**For Country Filtering:**
- Test filtering by different countries
- Verify the channel list updates correctly
- Test clearing the filter with "All"

## Requirements

- Android 5.0 (API level 21) or higher
- For TV remote navigation: Android TV or device with keyboard support

## Troubleshooting

**APK Installation Issues:**
- Enable "Install from Unknown Sources" in Android settings
- Make sure you're downloading the correct APK file

**Build Failures:**
- Check the GitHub Actions logs for specific errors
- Ensure all dependencies are properly specified in pubspec.yaml

**TV Remote Not Working:**
- Ensure you're testing on Android TV or emulator with keyboard
- The feature requires physical keyboard input (not on-screen keyboard)
12 changes: 11 additions & 1 deletion lib/model/channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ class Channel {
final String name;
final String logoUrl;
final String streamUrl;
final int number;
final String country;
final String? category;

Channel({required this.name, required this.logoUrl, required this.streamUrl});
Channel({
required this.name,
required this.logoUrl,
required this.streamUrl,
required this.number,
required this.country,
this.category,
});
}
83 changes: 81 additions & 2 deletions lib/provider/channels_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../model/channel.dart';
class ChannelsProvider with ChangeNotifier {
List<Channel> channels = [];
List<Channel> filteredChannels = [];
Map<String, List<Channel>> channelsByCountry = {};
String sourceUrl =
'https://raw.githubusercontent.com/FunctionError/PiratesTv/main/combined_playlist.m3u';

Expand All @@ -18,24 +19,43 @@ class ChannelsProvider with ChangeNotifier {
String? name;
String logoUrl = getDefaultLogoUrl();
String? streamUrl;
String country = 'India'; // Default country
String? category;
int channelNumber = 1;

for (String line in lines) {
if (line.startsWith('#EXTINF:')) {
name = extractChannelName(line);
logoUrl = extractLogoUrl(line) ?? getDefaultLogoUrl();
country = extractCountry(line);
category = extractCategory(line);
} else if (line.isNotEmpty) {
streamUrl = line;
if (name != null) {
channels.add(Channel(
final channel = Channel(
name: name,
logoUrl: logoUrl,
streamUrl: streamUrl,
));
number: channelNumber,
country: country,
category: category,
);
channels.add(channel);

// Group by country
if (!channelsByCountry.containsKey(country)) {
channelsByCountry[country] = [];
}
channelsByCountry[country]!.add(channel);

channelNumber++;
}
// Reset for next channel
name = null;
logoUrl = getDefaultLogoUrl();
streamUrl = null;
country = 'India';
category = null;
}
}
return channels;
Expand Down Expand Up @@ -67,6 +87,65 @@ class ChannelsProvider with ChangeNotifier {
return url.startsWith('https') || url.startsWith('http');
}

String extractCountry(String line) {
// Try to extract from group-title first
final groupTitleRegex = RegExp(r'group-title="([^"]+)"');
final groupMatch = groupTitleRegex.firstMatch(line);
if (groupMatch != null) {
return groupMatch.group(1) ?? 'India';
}

// Try to extract from tvg-country
final tvgCountryRegex = RegExp(r'tvg-country="([^"]+)"');
final countryMatch = tvgCountryRegex.firstMatch(line);
if (countryMatch != null) {
String countryCode = countryMatch.group(1) ?? '';
return convertCountryCodeToName(countryCode);
}

return 'India'; // Default
}

String extractCategory(String line) {
final categoryRegex = RegExp(r'tvg-category="([^"]+)"');
final match = categoryRegex.firstMatch(line);
return match?.group(1) ?? '';
}

static const Map<String, String> _countryCodes = {
'IN': 'India',
'US': 'United States',
'UK': 'United Kingdom',
'GB': 'United Kingdom',
'CA': 'Canada',
'AU': 'Australia',
'PK': 'Pakistan',
'BD': 'Bangladesh',
'NP': 'Nepal',
'LK': 'Sri Lanka',
'AE': 'UAE',
'SA': 'Saudi Arabia',
};

String convertCountryCodeToName(String code) {
return _countryCodes[code.toUpperCase()] ?? code;
}

Channel? getChannelByNumber(int number) {
return channels.cast<Channel?>().firstWhere(
(channel) => channel!.number == number,
orElse: () => null,
);
}

List<String> getCountries() {
return channelsByCountry.keys.toList()..sort();
}

List<Channel> getChannelsForCountry(String country) {
return channelsByCountry[country] ?? [];
}

List<Channel> filterChannels(String query) {
filteredChannels = channels
.where((channel) =>
Expand Down
Loading