mirror of
https://github.com/madari-media/madari-oss.git
synced 2026-03-11 17:15:39 +00:00
Project import generated by Copybara.
GitOrigin-RevId: d14f9c569b8c92f77b278db43796405a1b66b888
This commit is contained in:
parent
680fdb4c78
commit
a297699b6c
12 changed files with 234 additions and 107 deletions
6
.github/workflows/build-deploy.yaml
vendored
6
.github/workflows/build-deploy.yaml
vendored
|
|
@ -124,7 +124,7 @@ jobs:
|
|||
dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
- name: Build iOS
|
||||
run: flutter build ios --release --no-codesign
|
||||
run: make build_ipa
|
||||
|
||||
- name: Create and Pack IPA
|
||||
run: |
|
||||
|
|
@ -178,7 +178,7 @@ jobs:
|
|||
dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
- name: Build Linux
|
||||
run: flutter build linux --release
|
||||
run: make build_linux
|
||||
|
||||
- name: Upload Linux artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
|
@ -222,7 +222,7 @@ jobs:
|
|||
dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
- name: Build MacOS
|
||||
run: flutter build macos --release
|
||||
run: make build_mac
|
||||
|
||||
- name: Isolate the Build
|
||||
run: mkdir build/macos/Build/Products/Release/AppRelease
|
||||
|
|
|
|||
18
Makefile
18
Makefile
|
|
@ -1,5 +1,7 @@
|
|||
.PHONY: build schema build_web build_mac build_android build_windows build_ipa build_linux
|
||||
|
||||
BUILD_ID := $(or $(GITHUB_RUN_ID),dev)
|
||||
|
||||
.PHONY: build schema build_web build_mac
|
||||
build:
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
|
|
@ -7,13 +9,19 @@ schema:
|
|||
dart run drift_dev schema dump lib/database/database.dart drift_schemas/drift_schema_v1.json
|
||||
|
||||
build_web:
|
||||
flutter build web --target lib/main_web.dart --release --pwa-strategy none --wasm
|
||||
flutter build web --target lib/main_web.dart --release --pwa-strategy none --wasm --dart-define=BUILD_ID=$(BUILD_ID)
|
||||
|
||||
build_mac:
|
||||
flutter build macos --target lib/main.dart --release
|
||||
flutter build macos --target lib/main.dart --release --dart-define=BUILD_ID=$(BUILD_ID)
|
||||
|
||||
build_android:
|
||||
flutter build apk --release
|
||||
flutter build apk --release --dart-define=BUILD_ID=$(BUILD_ID)
|
||||
|
||||
build_windows:
|
||||
flutter build windows --release
|
||||
flutter build windows --release --dart-define=BUILD_ID=$(BUILD_ID)
|
||||
|
||||
build_ipa:
|
||||
flutter build ios --release --no-codesign --dart-define=BUILD_ID=$(BUILD_ID)
|
||||
|
||||
build_linux:
|
||||
flutter build linux --release --dart-define=BUILD_ID=$(BUILD_ID)
|
||||
|
|
|
|||
|
|
@ -313,6 +313,8 @@ class Meta extends LibraryItem {
|
|||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final int? traktId;
|
||||
|
||||
final dynamic externalIds;
|
||||
|
||||
String get imdbRating {
|
||||
return (imdbRating_ ?? "").toString();
|
||||
}
|
||||
|
|
@ -329,6 +331,7 @@ class Meta extends LibraryItem {
|
|||
this.cast,
|
||||
this.traktId,
|
||||
this.country,
|
||||
this.externalIds,
|
||||
this.description,
|
||||
this.genre,
|
||||
this.imdbRating_,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ class RenderStreamList extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _RenderStreamListState extends State<RenderStreamList> {
|
||||
Stream<List<StreamList>>? _stream;
|
||||
final Map<String, double> _downloadProgress = {};
|
||||
final Map<String, String> _downloadError = {};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:madari_client/features/connection/types/stremio.dart';
|
||||
import 'package:madari_client/features/connections/service/base_connection_service.dart';
|
||||
|
||||
|
|
@ -50,11 +51,27 @@ class StremioCard extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Video? get currentVideo {
|
||||
return (item as Meta).videos?.firstWhere((episode) {
|
||||
return (item as Meta).nextEpisode == episode.episode &&
|
||||
(item as Meta).nextSeason == episode.season;
|
||||
});
|
||||
}
|
||||
|
||||
bool get isInFuture {
|
||||
final video = currentVideo;
|
||||
return video != null &&
|
||||
video.firstAired != null &&
|
||||
video.firstAired!.isAfter(DateTime.now());
|
||||
}
|
||||
|
||||
_buildWideCard(BuildContext context, Meta meta) {
|
||||
if (meta.background == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final video = currentVideo;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
|
|
@ -67,6 +84,21 @@ class StremioCard extends StatelessWidget {
|
|||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (isInFuture)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.black,
|
||||
Colors.transparent,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
|
|
@ -90,6 +122,10 @@ class StremioCard extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text("S${meta.nextSeason} E${meta.nextEpisode}"),
|
||||
Text(
|
||||
"${meta.name}",
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
"${meta.nextEpisodeTitle}".trim(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
|
|
@ -98,6 +134,38 @@ class StremioCard extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (isInFuture)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Text(
|
||||
getRelativeDate(video!.firstAired!),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
if (isInFuture)
|
||||
const Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.calendar_month,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Positioned(
|
||||
child: Center(
|
||||
child: IconButton.filled(
|
||||
|
|
@ -176,13 +244,21 @@ class StremioCard extends StatelessWidget {
|
|||
|
||||
return Hero(
|
||||
tag: "$prefix${meta.type}${item.id}",
|
||||
child: AspectRatio(
|
||||
aspectRatio: 2 / 3,
|
||||
child: (backgroundImage == null)
|
||||
? Text("${meta.name}")
|
||||
: Stack(
|
||||
child: (backgroundImage == null)
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
Text(meta.name ?? "No name"),
|
||||
if (meta.description != null) Text(meta.description!),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: CachedNetworkImageProvider(
|
||||
|
|
@ -230,72 +306,92 @@ class StremioCard extends StatelessWidget {
|
|||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
if (meta.progress != null)
|
||||
const Positioned.fill(
|
||||
child: IconButton(
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
Icons.play_arrow,
|
||||
size: 24,
|
||||
),
|
||||
if (meta.progress != null)
|
||||
const Positioned.fill(
|
||||
child: IconButton(
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
Icons.play_arrow,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (meta.progress != null)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: LinearProgressIndicator(
|
||||
value: meta.progress,
|
||||
),
|
||||
),
|
||||
if (meta.nextEpisode != null && meta.nextSeason != null)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.grey,
|
||||
Colors.transparent,
|
||||
],
|
||||
begin: Alignment.bottomLeft,
|
||||
end: Alignment.topRight,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4, horizontal: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
meta.name ?? "",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
Text(
|
||||
"S${meta.nextSeason} E${meta.nextEpisode}",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (meta.progress != null)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: LinearProgressIndicator(
|
||||
value: meta.progress,
|
||||
),
|
||||
),
|
||||
if (meta.nextEpisode != null && meta.nextSeason != null)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.grey,
|
||||
Colors.transparent,
|
||||
],
|
||||
begin: Alignment.bottomLeft,
|
||||
end: Alignment.topRight,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4, horizontal: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
meta.name ?? "",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
Text(
|
||||
"S${meta.nextSeason} E${meta.nextEpisode}",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String getRelativeDate(DateTime date) {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final tomorrow = DateTime(now.year, now.month, now.day + 1);
|
||||
|
||||
final difference = date.difference(today).inDays;
|
||||
|
||||
if (date.isAtSameMomentAs(today)) {
|
||||
return "It's today!";
|
||||
} else if (date.isAtSameMomentAs(tomorrow)) {
|
||||
return "Coming up tomorrow!";
|
||||
} else if (difference > 1 && difference < 7) {
|
||||
return "Coming up in $difference days";
|
||||
} else if (difference >= 7 && difference < 14) {
|
||||
return "Coming up next ${DateFormat('EEEE').format(date)}";
|
||||
} else {
|
||||
return "On ${DateFormat('MM/dd/yyyy').format(date)}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import 'package:media_kit/media_kit.dart';
|
||||
|
||||
class TraktIntegrationVideo {
|
||||
Player player;
|
||||
|
||||
TraktIntegrationVideo({
|
||||
required this.player,
|
||||
});
|
||||
|
||||
initState() {}
|
||||
|
||||
dispose() {}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ class _TraktContainerState extends State<TraktContainer> {
|
|||
key: widget.loadId,
|
||||
config: QueryConfig(
|
||||
cacheDuration: const Duration(days: 30),
|
||||
refetchDuration: const Duration(minutes: 1),
|
||||
refetchDuration: const Duration(minutes: 10),
|
||||
storageDuration: const Duration(days: 30),
|
||||
),
|
||||
queryFn: () {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class TraktService {
|
|||
'Authorization': 'Bearer $_token',
|
||||
};
|
||||
|
||||
Future<List<LibraryItem>> getUpNextSeries({bool noUpNext = false}) async {
|
||||
Future<List<LibraryItem>> getUpNextSeries() async {
|
||||
await initStremioService();
|
||||
|
||||
if (!isEnabled()) {
|
||||
|
|
@ -98,17 +98,6 @@ class TraktService {
|
|||
final showId = show['show']['ids']['trakt'];
|
||||
final imdb = show['show']['ids']['imdb'];
|
||||
|
||||
if (noUpNext == true) {
|
||||
final meta = await stremioService!.getItemById(
|
||||
Meta(
|
||||
type: "series",
|
||||
id: imdb,
|
||||
),
|
||||
);
|
||||
|
||||
return (meta as Meta).copyWith();
|
||||
}
|
||||
|
||||
try {
|
||||
final progressResponse = await http.get(
|
||||
Uri.parse('$_baseUrl/shows/$showId/progress/watched'),
|
||||
|
|
@ -123,11 +112,13 @@ class TraktService {
|
|||
|
||||
final nextEpisode = progress['next_episode'];
|
||||
|
||||
print(nextEpisode);
|
||||
|
||||
if (nextEpisode != null && imdb != null) {
|
||||
final item = await stremioService!.getItemById(
|
||||
Meta(type: "series", id: imdb),
|
||||
Meta(
|
||||
type: "series",
|
||||
id: imdb,
|
||||
externalIds: show['show']['ids'],
|
||||
),
|
||||
);
|
||||
|
||||
item as Meta;
|
||||
|
|
@ -203,7 +194,7 @@ class TraktService {
|
|||
}).toList(),
|
||||
);
|
||||
|
||||
return result.map((res) {
|
||||
return result.sublist(0, 20).map((res) {
|
||||
Meta returnValue = res as Meta;
|
||||
|
||||
if (progress.containsKey(res.id)) {
|
||||
|
|
@ -325,15 +316,23 @@ class TraktService {
|
|||
final recommendedShows =
|
||||
json.decode(recommendationsResponse.body) as List;
|
||||
|
||||
final result = await stremioService!.getBulkItem(
|
||||
recommendedShows.map((show) {
|
||||
final imdb = show['ids']['imdb'];
|
||||
return Meta(
|
||||
type: "series",
|
||||
id: imdb,
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
final result = (await stremioService!.getBulkItem(
|
||||
recommendedShows
|
||||
.map((show) {
|
||||
final imdb = show['ids']?['imdb'];
|
||||
|
||||
if (imdb == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Meta(
|
||||
type: "series",
|
||||
id: imdb,
|
||||
);
|
||||
})
|
||||
.whereType<Meta>()
|
||||
.toList(),
|
||||
));
|
||||
|
||||
return result;
|
||||
} catch (e, stack) {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ class MoreContainer extends StatelessWidget {
|
|||
},
|
||||
hideTrailing: true,
|
||||
),
|
||||
Text(
|
||||
"Version ${const String.fromEnvironment('BUILD_ID', defaultValue: "Dev")}",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ final Map<String, List<ExternalMediaPlayer>> externalPlayers = {
|
|||
id: "com.brouken.player",
|
||||
name: "JustPlayer",
|
||||
),
|
||||
ExternalMediaPlayer(
|
||||
id: "xyz.skybox.player",
|
||||
name: "Skybox",
|
||||
),
|
||||
],
|
||||
"ios": [
|
||||
ExternalMediaPlayer(
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@ BEGIN
|
|||
VALUE "CompanyName", "media.madari" "\0"
|
||||
VALUE "FileDescription", "madari_client" "\0"
|
||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||
VALUE "InternalName", "madari_client" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2024 media.madari. All rights reserved." "\0"
|
||||
VALUE "InternalName", "Madari" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2025 media.madari. All rights reserved." "\0"
|
||||
VALUE "OriginalFilename", "madari_client.exe" "\0"
|
||||
VALUE "ProductName", "madari_client" "\0"
|
||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
|||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(1280, 720);
|
||||
if (!window.Create(L"madari_client", origin, size)) {
|
||||
if (!window.Create(L"Madari", origin, size)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
|
|
|
|||
Loading…
Reference in a new issue