Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
836a0886e2 | |||
1cd10b3809 | |||
43c42ac0d5 | |||
bf4e0fa493 | |||
27476fbc1d | |||
8e9f0eb1b8 | |||
f083fc8e8f | |||
9a716018fc |
@ -11,12 +11,12 @@ android {
|
|||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
<application
|
<application
|
||||||
android:label="f0ckapp"
|
android:label="f0ckapp"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:enableOnBackInvokedCallback="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||||
|
</adaptive-icon>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 3.5 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
Normal file
After Width: | Height: | Size: 839 B |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 1.9 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
Normal file
After Width: | Height: | Size: 446 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 5.2 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 72 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 128 KiB |
After Width: | Height: | Size: 128 KiB |
BIN
android/app/src/main/res/play_store_512.png
Normal file
After Width: | Height: | Size: 327 KiB |
BIN
assets/images/f0ck_small.webp
Normal file
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 192 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.8 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 6.9 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png
Normal file
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 2.2 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png
Normal file
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png
Normal file
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 43 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
Normal file
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 37 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png
Normal file
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 1.0 MiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png
Normal file
After Width: | Height: | Size: 11 KiB |
@ -1,42 +1,33 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/providers/MediaProvider.dart';
|
|
||||||
import 'package:f0ckapp/providers/ThemeProvider.dart';
|
|
||||||
import 'package:f0ckapp/screens/MediaGrid.dart';
|
import 'package:f0ckapp/screens/MediaGrid.dart';
|
||||||
import 'package:f0ckapp/utils/AppVersion.dart';
|
import 'package:f0ckapp/utils/AppVersion.dart';
|
||||||
|
import 'package:f0ckapp/providers/ThemeProvider.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
await AppVersion.init();
|
await AppVersion.init();
|
||||||
|
|
||||||
runApp(
|
runApp(ProviderScope(child: const F0ckApp()));
|
||||||
MultiProvider(
|
|
||||||
providers: [
|
|
||||||
ChangeNotifierProvider(create: (context) => ThemeProvider()),
|
|
||||||
ChangeNotifierProvider(create: (context) => MediaProvider())
|
|
||||||
],
|
|
||||||
child: F0ckApp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class F0ckApp extends StatelessWidget {
|
class F0ckApp extends ConsumerWidget {
|
||||||
const F0ckApp({super.key});
|
const F0ckApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final themeProvider = Provider.of<ThemeProvider>(context);
|
return Consumer(
|
||||||
|
builder: (context, ref, _) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: themeProvider.themeData,
|
theme: ref.watch(themeNotifierProvider),
|
||||||
home: Scaffold(
|
home: const MediaGrid(),
|
||||||
body: MediaGrid(),
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,95 +1,142 @@
|
|||||||
import 'package:f0ckapp/services/Api.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/models/MediaItem.dart';
|
import 'package:f0ckapp/models/MediaItem.dart';
|
||||||
|
import 'package:f0ckapp/services/Api.dart';
|
||||||
|
|
||||||
class MediaProvider extends ChangeNotifier {
|
const List<String> mediaTypes = ["alles", "image", "video", "audio"];
|
||||||
int _typeid = 0;
|
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
|
||||||
int _mode = 0;
|
const _unsetTag = Object();
|
||||||
bool _random = false;
|
|
||||||
String? _tag;
|
|
||||||
int _crossAxisCount = 0;
|
|
||||||
List<MediaItem> _mediaItems = [];
|
|
||||||
bool _isLoading = false;
|
|
||||||
|
|
||||||
List<String> types = ["alles", "image", "video", "audio"];
|
class MediaState {
|
||||||
List<String> modes = ["sfw", "nsfw", "untagged", "all"];
|
final int typeIndex;
|
||||||
|
final int modeIndex;
|
||||||
|
final bool random;
|
||||||
|
final String? tag;
|
||||||
|
final int crossAxisCount;
|
||||||
|
final List<MediaItem> mediaItems;
|
||||||
|
final bool isLoading;
|
||||||
|
final bool muted;
|
||||||
|
|
||||||
String get type => types[_typeid];
|
const MediaState({
|
||||||
int get typeid => _typeid;
|
this.typeIndex = 0,
|
||||||
int get mode => _mode;
|
this.modeIndex = 0,
|
||||||
bool get random => _random;
|
this.random = false,
|
||||||
String? get tag => _tag;
|
this.tag,
|
||||||
int get crossAxisCount => _crossAxisCount;
|
this.crossAxisCount = 0,
|
||||||
List<MediaItem> get mediaItems => _mediaItems;
|
this.mediaItems = const [],
|
||||||
bool get isLoading => _isLoading;
|
this.isLoading = false,
|
||||||
Function get resetMedia => _resetMedia;
|
this.muted = false,
|
||||||
|
});
|
||||||
|
|
||||||
void setType(String type) {
|
MediaState replace({
|
||||||
_typeid = types.indexOf(type);
|
int? typeIndex,
|
||||||
_resetMedia();
|
int? modeIndex,
|
||||||
|
bool? random,
|
||||||
|
Object? tag = _unsetTag,
|
||||||
|
int? crossAxisCount,
|
||||||
|
List<MediaItem>? mediaItems,
|
||||||
|
bool? isLoading,
|
||||||
|
bool? muted,
|
||||||
|
}) {
|
||||||
|
return MediaState(
|
||||||
|
typeIndex: typeIndex ?? this.typeIndex,
|
||||||
|
modeIndex: modeIndex ?? this.modeIndex,
|
||||||
|
random: random ?? this.random,
|
||||||
|
tag: identical(tag, _unsetTag) ? this.tag : tag as String?,
|
||||||
|
crossAxisCount: crossAxisCount ?? this.crossAxisCount,
|
||||||
|
mediaItems: mediaItems ?? this.mediaItems,
|
||||||
|
isLoading: isLoading ?? this.isLoading,
|
||||||
|
muted: muted ?? this.muted,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MediaNotifier extends StateNotifier<MediaState> {
|
||||||
|
final _storage = const FlutterSecureStorage(
|
||||||
|
aOptions: AndroidOptions(encryptedSharedPreferences: true),
|
||||||
|
);
|
||||||
|
|
||||||
|
MediaNotifier() : super(const MediaState()) {
|
||||||
|
_loadMutedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMode(int mode) {
|
Future<void> _loadMutedState() async {
|
||||||
_mode = mode;
|
final storedMuted = await _storage.read(key: 'muted');
|
||||||
_resetMedia();
|
final isMuted = storedMuted == 'true';
|
||||||
|
state = state.replace(muted: isMuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveMutedState() async {
|
||||||
|
await _storage.write(key: 'muted', value: state.muted.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setType(String type) {
|
||||||
|
final newIndex = mediaTypes.indexOf(type);
|
||||||
|
state = state.replace(typeIndex: newIndex);
|
||||||
|
resetMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMode(int modeIndex) {
|
||||||
|
state = state.replace(modeIndex: modeIndex);
|
||||||
|
resetMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleRandom() {
|
void toggleRandom() {
|
||||||
_random = !_random;
|
state = state.replace(random: !state.random);
|
||||||
_resetMedia();
|
resetMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTag(String? tag) {
|
void setTag(String? tag) {
|
||||||
_tag = tag;
|
state = state.replace(tag: tag);
|
||||||
_resetMedia();
|
resetMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCrossAxisCount(int crossAxisCount) {
|
void setCrossAxisCount(int count) {
|
||||||
_crossAxisCount = crossAxisCount;
|
state = state.replace(crossAxisCount: count);
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMediaItems(List<MediaItem> mediaItems) {
|
void resetMedia() {
|
||||||
if (_mediaItems != mediaItems) {
|
state = state.replace(mediaItems: []);
|
||||||
_mediaItems.clear();
|
|
||||||
_mediaItems.addAll(mediaItems);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void addMediaItems(List<MediaItem> newItems) {
|
|
||||||
_mediaItems.addAll(newItems);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resetMedia() {
|
|
||||||
_mediaItems.clear();
|
|
||||||
notifyListeners();
|
|
||||||
loadMedia();
|
loadMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadMedia({bool notify = true}) async {
|
void addMediaItems(List<MediaItem> newItems) {
|
||||||
if (_isLoading) return;
|
final updated = List<MediaItem>.from(state.mediaItems)..addAll(newItems);
|
||||||
_isLoading = true;
|
state = state.replace(mediaItems: updated);
|
||||||
if (notify) notifyListeners();
|
}
|
||||||
|
|
||||||
|
Future<void> loadMedia() async {
|
||||||
|
if (state.isLoading) return;
|
||||||
|
|
||||||
|
state = state.replace(isLoading: true);
|
||||||
try {
|
try {
|
||||||
|
final older = state.mediaItems.isNotEmpty
|
||||||
|
? state.mediaItems.last.id
|
||||||
|
: null;
|
||||||
final newMedia = await fetchMedia(
|
final newMedia = await fetchMedia(
|
||||||
older: _mediaItems.isNotEmpty ? _mediaItems.last.id : null,
|
older: older,
|
||||||
type: type,
|
type: mediaTypes[state.typeIndex],
|
||||||
mode: mode,
|
mode: state.modeIndex,
|
||||||
random: random,
|
random: state.random,
|
||||||
tag: tag,
|
tag: state.tag,
|
||||||
);
|
);
|
||||||
|
if (newMedia.isNotEmpty) {
|
||||||
if(_mediaItems != newMedia) {
|
|
||||||
addMediaItems(newMedia);
|
addMediaItems(newMedia);
|
||||||
if (notify) notifyListeners();
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Fehler beim Laden der Medien: $e');
|
print('Fehler beim Laden der Medien: $e');
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
state = state.replace(isLoading: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void toggleMute() {
|
||||||
|
state = state.replace(muted: !state.muted);
|
||||||
|
_saveMutedState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final mediaProvider = StateNotifierProvider<MediaNotifier, MediaState>(
|
||||||
|
(ref) => MediaNotifier(),
|
||||||
|
);
|
||||||
|
@ -1,132 +1,240 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class ThemeNotifier extends StateNotifier<ThemeData> {
|
||||||
|
final FlutterSecureStorage secureStorage;
|
||||||
|
|
||||||
|
ThemeNotifier({required this.secureStorage}) : super(f0ckTheme) {
|
||||||
|
_loadTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadTheme() async {
|
||||||
|
String? savedThemeName = await secureStorage.read(key: 'theme');
|
||||||
|
if (savedThemeName != null) {
|
||||||
|
final customTheme = themes.firstWhere(
|
||||||
|
(t) => t.name == savedThemeName,
|
||||||
|
orElse: () => CustomTheme(name: 'f0ck', theme: f0ckTheme),
|
||||||
|
);
|
||||||
|
state = customTheme.theme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateTheme(String themeName) async {
|
||||||
|
await secureStorage.write(key: 'theme', value: themeName);
|
||||||
|
final newTheme = themes.firstWhere(
|
||||||
|
(t) => t.name == themeName,
|
||||||
|
orElse: () => CustomTheme(name: 'f0ck', theme: f0ckTheme),
|
||||||
|
);
|
||||||
|
state = newTheme.theme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final themeNotifierProvider = StateNotifierProvider<ThemeNotifier, ThemeData>((
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
return ThemeNotifier(
|
||||||
|
secureStorage: FlutterSecureStorage(
|
||||||
|
aOptions: AndroidOptions(encryptedSharedPreferences: true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
class CustomTheme {
|
||||||
|
final String name;
|
||||||
|
final ThemeData theme;
|
||||||
|
|
||||||
|
CustomTheme({required this.name, required this.theme});
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<CustomTheme> themes = [
|
||||||
|
CustomTheme(name: 'f0ck', theme: f0ckTheme),
|
||||||
|
CustomTheme(name: 'Paper', theme: paperTheme),
|
||||||
|
CustomTheme(name: 'Orange', theme: orangeTheme),
|
||||||
|
CustomTheme(name: 'Amoled', theme: amoledTheme),
|
||||||
|
CustomTheme(name: 'f0ck95', theme: f0ck95Theme),
|
||||||
|
CustomTheme(name: 'f0ck95d', theme: f0ck95dTheme),
|
||||||
|
];
|
||||||
|
|
||||||
final ThemeData f0ckTheme = ThemeData(
|
final ThemeData f0ckTheme = ThemeData(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primaryColor: Color(0xFF9FFF00),
|
primaryColor: const Color(0xFF9FFF00),
|
||||||
scaffoldBackgroundColor: Color(0xFF000000),
|
scaffoldBackgroundColor: const Color(0xFF000000),
|
||||||
colorScheme: ColorScheme.dark(
|
colorScheme: const ColorScheme.dark(
|
||||||
primary: Color(0xFF9FFF00),
|
primary: Color(0xFF9FFF00),
|
||||||
secondary: Color(0xFF262626),
|
secondary: Color(0xFF262626),
|
||||||
surface: Color(0xFF232323),
|
surface: Color(0xFF232323),
|
||||||
onPrimary: Color(0xFF000000),
|
onPrimary: Color(0xFF000000),
|
||||||
onSecondary: Color(0xFFFFFFFF),
|
onSecondary: Colors.white,
|
||||||
onSurface: Color(0xFFFFFFFF),
|
onSurface: Colors.white,
|
||||||
),
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: Color(0xFF2B2B2B),
|
backgroundColor: Color(0xFF2B2B2B),
|
||||||
foregroundColor: Color(0xFF9FFF00),
|
foregroundColor: Color(0xFF9FFF00),
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
),
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: const TextTheme(
|
||||||
bodyLarge: TextStyle(fontFamily: 'VCR', color: Color(0xFFFFFFFF)),
|
bodyLarge: TextStyle(color: Colors.white),
|
||||||
bodyMedium: TextStyle(fontFamily: 'monospace', color: Color(0xFFFFFFFF)),
|
bodyMedium: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
buttonTheme: ButtonThemeData(
|
||||||
buttonColor: Color(0xFF9FFF00),
|
buttonColor: const Color(0xFF9FFF00),
|
||||||
textTheme: ButtonTextTheme.primary,
|
textTheme: ButtonTextTheme.primary,
|
||||||
),
|
),
|
||||||
scrollbarTheme: ScrollbarThemeData(
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
thumbColor: WidgetStateProperty.all(Color(0xFF2B2B2B)),
|
thumbColor: WidgetStatePropertyAll<Color>(Color(0xFF2B2B2B)),
|
||||||
trackColor: WidgetStateProperty.all(Color(0xFF424242)),
|
trackColor: WidgetStatePropertyAll<Color>(Color(0xFF424242)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final ThemeData paperTheme = ThemeData(
|
final ThemeData paperTheme = ThemeData(
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
primaryColor: Color(0xFF000000),
|
primaryColor: const Color(0xFF000000),
|
||||||
scaffoldBackgroundColor: Color(0xFFFFFFFF),
|
scaffoldBackgroundColor: const Color(0xFFFFFFFF),
|
||||||
colorScheme: ColorScheme.light(
|
colorScheme: const ColorScheme.light(
|
||||||
primary: Color(0xFF000000),
|
primary: Color(0xFF000000),
|
||||||
secondary: Color(0xFF262626),
|
secondary: Color(0xFF262626),
|
||||||
surface: Color(0xFFFFFFFF),
|
surface: Color(0xFFFFFFFF),
|
||||||
onPrimary: Color(0xFFFFFFFF),
|
onPrimary: Colors.white,
|
||||||
onSecondary: Color(0xFFFFFFFF),
|
onSecondary: Colors.white,
|
||||||
onSurface: Color(0xFF000000),
|
onSurface: Color(0xFF000000),
|
||||||
),
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: Color(0xFFFFFFFF),
|
backgroundColor: Color(0xFFFFFFFF),
|
||||||
foregroundColor: Color(0xFF000000),
|
foregroundColor: Color(0xFF000000),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: const TextTheme(
|
||||||
bodyLarge: TextStyle(fontFamily: 'VCR', color: Color(0xFF000000)),
|
bodyLarge: TextStyle(color: Color(0xFF000000)),
|
||||||
bodyMedium: TextStyle(fontFamily: 'monospace', color: Color(0xFF000000)),
|
bodyMedium: TextStyle(color: Color(0xFF000000)),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
buttonTheme: ButtonThemeData(
|
||||||
buttonColor: Color(0xFF000000),
|
buttonColor: const Color(0xFF000000),
|
||||||
textTheme: ButtonTextTheme.primary,
|
textTheme: ButtonTextTheme.primary,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final ThemeData orangeTheme = ThemeData(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
primaryColor: const Color(0xFFFF6F00),
|
||||||
|
scaffoldBackgroundColor: const Color(0xFF171717),
|
||||||
|
colorScheme: const ColorScheme.dark(
|
||||||
|
primary: Color(0xFFFF6F00),
|
||||||
|
secondary: Color(0xFF262626),
|
||||||
|
surface: Color(0xFF232323),
|
||||||
|
onPrimary: Colors.white,
|
||||||
|
onSecondary: Colors.white,
|
||||||
|
onSurface: Colors.white,
|
||||||
|
),
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
backgroundColor: Color(0xFF2B2B2B),
|
||||||
|
foregroundColor: Color(0xFFFF6F00),
|
||||||
|
elevation: 2,
|
||||||
|
),
|
||||||
|
textTheme: const TextTheme(
|
||||||
|
bodyLarge: TextStyle(color: Colors.white),
|
||||||
|
bodyMedium: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
buttonTheme: ButtonThemeData(
|
||||||
|
buttonColor: const Color(0xFFFF6F00),
|
||||||
|
textTheme: ButtonTextTheme.primary,
|
||||||
|
),
|
||||||
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
|
thumbColor: WidgetStatePropertyAll<Color>(Color(0xFF2B2B2B)),
|
||||||
|
trackColor: WidgetStatePropertyAll<Color>(Color(0xFF424242)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final ThemeData amoledTheme = ThemeData(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
primaryColor: const Color(0xFFFFFFFF),
|
||||||
|
scaffoldBackgroundColor: const Color(0xFF000000),
|
||||||
|
canvasColor: const Color(0xFF000000),
|
||||||
|
cardColor: const Color(0xFF000000),
|
||||||
|
colorScheme: const ColorScheme.dark(
|
||||||
|
primary: Color(0xFFFFFFFF),
|
||||||
|
secondary: Color(0xFF1F1F1F),
|
||||||
|
surface: Color(0xFF000000),
|
||||||
|
onPrimary: Color(0xFF000000),
|
||||||
|
onSecondary: Colors.white,
|
||||||
|
onSurface: Colors.white,
|
||||||
|
),
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
backgroundColor: Color(0xFF000000),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
elevation: 2,
|
||||||
|
),
|
||||||
|
textTheme: const TextTheme(
|
||||||
|
bodyLarge: TextStyle(color: Colors.white),
|
||||||
|
bodyMedium: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
buttonTheme: ButtonThemeData(
|
||||||
|
buttonColor: const Color(0xFFFFFFFF),
|
||||||
|
textTheme: ButtonTextTheme.primary,
|
||||||
|
),
|
||||||
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
|
thumbColor: WidgetStateProperty.all<Color>(const Color(0xFF1D1C1C)),
|
||||||
|
trackColor: WidgetStateProperty.all<Color>(const Color(0xFF000000)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final ThemeData f0ck95Theme = ThemeData(
|
final ThemeData f0ck95Theme = ThemeData(
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
primaryColor: Color(0xFFC0C0C0),
|
primaryColor: const Color(0xFFC0C0C0),
|
||||||
scaffoldBackgroundColor: Color(0xFF008080),
|
scaffoldBackgroundColor: const Color(0xFF008080),
|
||||||
colorScheme: ColorScheme.light(
|
colorScheme: const ColorScheme.light(
|
||||||
primary: Color(0xFFC0C0C0),
|
primary: Color(0xFFC0C0C0),
|
||||||
secondary: Color(0xFF808080),
|
secondary: Color(0xFF808080),
|
||||||
surface: Color(0xFFC0C0C0),
|
surface: Color(0xFFC0C0C0),
|
||||||
onPrimary: Color(0xFF000000),
|
onPrimary: Colors.black,
|
||||||
onSecondary: Color(0xFFFFFFFF),
|
onSecondary: Colors.white,
|
||||||
),
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: Color(0xFFC0C0C0),
|
backgroundColor: Color(0xFFC0C0C0),
|
||||||
foregroundColor: Color(0xFF000000),
|
foregroundColor: Colors.black,
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
),
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: const TextTheme(
|
||||||
bodyLarge: TextStyle(fontFamily: 'VCR', color: Color(0xFF000000)),
|
bodyLarge: TextStyle(color: Colors.black),
|
||||||
bodyMedium: TextStyle(fontFamily: 'monospace', color: Color(0xFF000000)),
|
bodyMedium: TextStyle(color: Colors.black),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
buttonTheme: ButtonThemeData(
|
||||||
buttonColor: Color(0xFF000000),
|
buttonColor: Colors.black,
|
||||||
textTheme: ButtonTextTheme.primary,
|
textTheme: ButtonTextTheme.primary,
|
||||||
),
|
),
|
||||||
scrollbarTheme: ScrollbarThemeData(
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
thumbColor: WidgetStateProperty.all(Color(0xFF2B2B2B)),
|
thumbColor: WidgetStatePropertyAll<Color>(Color(0xFF2B2B2B)),
|
||||||
trackColor: WidgetStateProperty.all(Color(0xFF424242)),
|
trackColor: WidgetStatePropertyAll<Color>(Color(0xFF424242)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final ThemeData f0ck95dTheme = ThemeData(
|
final ThemeData f0ck95dTheme = ThemeData(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primaryColor: Color(0xFFFFFFFF),
|
primaryColor: const Color(0xFFFFFFFF),
|
||||||
scaffoldBackgroundColor: Color(0xFF0E0F0F),
|
scaffoldBackgroundColor: const Color(0xFF0E0F0F),
|
||||||
colorScheme: ColorScheme.dark(
|
colorScheme: const ColorScheme.dark(
|
||||||
primary: Color(0xFFFFFFFF),
|
primary: Color(0xFFFFFFFF),
|
||||||
secondary: Color(0xFFC0C0C0),
|
secondary: Color(0xFFC0C0C0),
|
||||||
surface: Color(0xFF333131),
|
surface: Color(0xFF333131),
|
||||||
onPrimary: Color(0xFF000000),
|
onPrimary: Colors.black,
|
||||||
onSecondary: Color(0xFFFFFFFF),
|
onSecondary: Colors.white,
|
||||||
),
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: Color(0xFF0B0A0A),
|
backgroundColor: Color(0xFF0B0A0A),
|
||||||
foregroundColor: Color(0xFFFFFFFF),
|
foregroundColor: Colors.white,
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
),
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: const TextTheme(
|
||||||
bodyLarge: TextStyle(fontFamily: 'VCR', color: Color(0xFFFFFFFF)),
|
bodyLarge: TextStyle(color: Colors.white),
|
||||||
bodyMedium: TextStyle(fontFamily: 'monospace', color: Color(0xFFFFFFFF)),
|
bodyMedium: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
buttonTheme: ButtonThemeData(
|
||||||
buttonColor: Color(0xFFFFFFFF),
|
buttonColor: Colors.white,
|
||||||
textTheme: ButtonTextTheme.primary,
|
textTheme: ButtonTextTheme.primary,
|
||||||
),
|
),
|
||||||
scrollbarTheme: ScrollbarThemeData(
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
thumbColor: WidgetStateProperty.all(Color(0xFF2B2B2B)),
|
thumbColor: WidgetStatePropertyAll<Color>(Color(0xFF2B2B2B)),
|
||||||
trackColor: WidgetStateProperty.all(Color(0xFF424242)),
|
trackColor: WidgetStatePropertyAll<Color>(Color(0xFF424242)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ThemeProvider extends ChangeNotifier {
|
|
||||||
ThemeData _themeData = f0ck95dTheme;
|
|
||||||
|
|
||||||
ThemeData get themeData => _themeData;
|
|
||||||
|
|
||||||
/*void toggleTheme() {
|
|
||||||
_themeData = _themeData == lightTheme ? darkTheme : lightTheme;
|
|
||||||
notifyListeners();
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
import 'dart:convert';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/models/MediaItem.dart';
|
import 'package:f0ckapp/models/MediaItem.dart';
|
||||||
import 'package:f0ckapp/services/Api.dart';
|
|
||||||
import 'package:f0ckapp/widgets/VideoWidget.dart';
|
import 'package:f0ckapp/widgets/VideoWidget.dart';
|
||||||
import 'package:f0ckapp/utils/SmartRefreshIndicator.dart';
|
import 'package:f0ckapp/utils/SmartRefreshIndicator.dart';
|
||||||
import 'package:f0ckapp/utils/PageTransformer.dart';
|
import 'package:f0ckapp/utils/PageTransformer.dart';
|
||||||
import 'package:f0ckapp/providers/MediaProvider.dart';
|
import 'package:f0ckapp/providers/MediaProvider.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
|
||||||
|
|
||||||
class DetailView extends StatefulWidget {
|
class DetailView extends ConsumerStatefulWidget {
|
||||||
final int initialItemId;
|
final int initialItemId;
|
||||||
|
|
||||||
const DetailView({super.key, required this.initialItemId});
|
const DetailView({super.key, required this.initialItemId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State createState() => _DetailViewState();
|
ConsumerState<DetailView> createState() => _DetailViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DetailViewState extends State<DetailView> {
|
class _DetailViewState extends ConsumerState<DetailView> {
|
||||||
late PageController _pageController;
|
late PageController _pageController;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
@ -29,15 +31,14 @@ class _DetailViewState extends State<DetailView> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final provider = Provider.of<MediaProvider>(context, listen: false);
|
|
||||||
|
|
||||||
final initialIndex = provider.mediaItems.indexWhere(
|
final mediaState = ref.read(mediaProvider);
|
||||||
|
final initialIndex = mediaState.mediaItems.indexWhere(
|
||||||
(item) => item.id == widget.initialItemId,
|
(item) => item.id == widget.initialItemId,
|
||||||
);
|
);
|
||||||
|
|
||||||
_pageController = PageController(initialPage: initialIndex);
|
_pageController = PageController(initialPage: initialIndex);
|
||||||
|
|
||||||
_currentIndex = initialIndex;
|
_currentIndex = initialIndex;
|
||||||
|
|
||||||
_pageController.addListener(() {
|
_pageController.addListener(() {
|
||||||
setState(() => _currentIndex = _pageController.page?.round() ?? 0);
|
setState(() => _currentIndex = _pageController.page?.round() ?? 0);
|
||||||
});
|
});
|
||||||
@ -46,16 +47,15 @@ class _DetailViewState extends State<DetailView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _preloadAdjacentMedia(int index) async {
|
void _preloadAdjacentMedia(int index) async {
|
||||||
final provider = Provider.of<MediaProvider>(context, listen: false);
|
final mediaState = ref.read(mediaProvider);
|
||||||
if (index + 1 < provider.mediaItems.length) {
|
if (index + 1 < mediaState.mediaItems.length) {
|
||||||
final nextUrl = provider.mediaItems[index + 1].mediaUrl;
|
final nextUrl = mediaState.mediaItems[index + 1].mediaUrl;
|
||||||
if (await DefaultCacheManager().getFileFromCache(nextUrl) == null) {
|
if (await DefaultCacheManager().getFileFromCache(nextUrl) == null) {
|
||||||
await DefaultCacheManager().downloadFile(nextUrl);
|
await DefaultCacheManager().downloadFile(nextUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index - 1 >= 0) {
|
if (index - 1 >= 0) {
|
||||||
final prevUrl = provider.mediaItems[index - 1].mediaUrl;
|
final prevUrl = mediaState.mediaItems[index - 1].mediaUrl;
|
||||||
if (await DefaultCacheManager().getFileFromCache(prevUrl) == null) {
|
if (await DefaultCacheManager().getFileFromCache(prevUrl) == null) {
|
||||||
await DefaultCacheManager().downloadFile(prevUrl);
|
await DefaultCacheManager().downloadFile(prevUrl);
|
||||||
}
|
}
|
||||||
@ -66,19 +66,8 @@ class _DetailViewState extends State<DetailView> {
|
|||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
setState(() => isLoading = true);
|
setState(() => isLoading = true);
|
||||||
|
|
||||||
final provider = Provider.of<MediaProvider>(context, listen: false);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final newMedia = await fetchMedia(
|
await ref.read(mediaProvider.notifier).loadMedia();
|
||||||
older: provider.mediaItems.last.id,
|
|
||||||
type: provider.type,
|
|
||||||
mode: provider.mode,
|
|
||||||
random: provider.random,
|
|
||||||
tag: provider.tag,
|
|
||||||
);
|
|
||||||
if (mounted && newMedia.isNotEmpty) {
|
|
||||||
setState(() => provider.mediaItems.addAll(newMedia));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_showError("Fehler beim Laden der Medien: $e");
|
_showError("Fehler beim Laden der Medien: $e");
|
||||||
} finally {
|
} finally {
|
||||||
@ -88,42 +77,63 @@ class _DetailViewState extends State<DetailView> {
|
|||||||
|
|
||||||
void _showError(String message) {
|
void _showError(String message) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(context)
|
||||||
context,
|
..removeCurrentSnackBar()
|
||||||
).showSnackBar(SnackBar(content: Text(message)));
|
..showSnackBar(SnackBar(content: Text(message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pageController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = Provider.of<MediaProvider>(context);
|
final mediaState = ref.watch(mediaProvider);
|
||||||
|
final mediaNotifier = ref.read(mediaProvider.notifier);
|
||||||
|
|
||||||
|
if (mediaState.mediaItems.isEmpty) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: const Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: Text(
|
title: Text('f0ck #${mediaState.mediaItems[_currentIndex].id}'),
|
||||||
'f0ck #${provider.mediaItems.elementAt(_currentIndex).id.toString()}',
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.download),
|
||||||
|
onPressed: () {
|
||||||
|
// wip
|
||||||
|
},
|
||||||
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
onSelected: (value) {
|
onSelected: (value) async {
|
||||||
final item = provider.mediaItems.elementAt(_currentIndex);
|
final item = mediaState.mediaItems[_currentIndex];
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'media':
|
case 'media':
|
||||||
final params = ShareParams(
|
File file = await DefaultCacheManager().getSingleFile(
|
||||||
files: [
|
item.mediaUrl,
|
||||||
XFile.fromData(
|
|
||||||
utf8.encode(item.mediaUrl),
|
|
||||||
mimeType: item.mime,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
SharePlus.instance.share(params);
|
Uint8List bytes = await file.readAsBytes();
|
||||||
|
final params = ShareParams(
|
||||||
|
files: [XFile.fromData(bytes, mimeType: item.mime)],
|
||||||
|
);
|
||||||
|
await SharePlus.instance.share(params);
|
||||||
break;
|
break;
|
||||||
case 'direct_link':
|
case 'direct_link':
|
||||||
SharePlus.instance.share(ShareParams(text: item.mediaUrl));
|
await SharePlus.instance.share(
|
||||||
|
ShareParams(text: item.mediaUrl),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'post_link':
|
case 'post_link':
|
||||||
SharePlus.instance.share(ShareParams(text: item.postUrl));
|
await SharePlus.instance.share(
|
||||||
|
ShareParams(text: item.postUrl),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -131,26 +141,26 @@ class _DetailViewState extends State<DetailView> {
|
|||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'media',
|
value: 'media',
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Icon(Icons.image),
|
leading: const Icon(Icons.image),
|
||||||
title: Text('Als Datei'),
|
title: const Text('Als Datei'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'direct_link',
|
value: 'direct_link',
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Icon(Icons.link),
|
leading: const Icon(Icons.link),
|
||||||
title: Text('Link zum Bild'),
|
title: const Text('Link zur Datei'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'post_link',
|
value: 'post_link',
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Icon(Icons.article),
|
leading: const Icon(Icons.article),
|
||||||
title: Text('Link zum Post'),
|
title: const Text('Link zum f0ck'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
icon: Icon(Icons.share),
|
icon: const Icon(Icons.share),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -158,9 +168,8 @@ class _DetailViewState extends State<DetailView> {
|
|||||||
children: [
|
children: [
|
||||||
PageTransformer(
|
PageTransformer(
|
||||||
controller: _pageController,
|
controller: _pageController,
|
||||||
pages: provider.mediaItems.map((item) {
|
pages: mediaState.mediaItems.map((item) {
|
||||||
int itemIndex = provider.mediaItems.indexOf(item);
|
int itemIndex = mediaState.mediaItems.indexOf(item);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: SmartRefreshIndicator(
|
child: SmartRefreshIndicator(
|
||||||
onRefresh: _loadMoreMedia,
|
onRefresh: _loadMoreMedia,
|
||||||
@ -171,24 +180,24 @@ class _DetailViewState extends State<DetailView> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
persistentFooterButtons: provider.tag != null
|
persistentFooterButtons: mediaState.tag != null
|
||||||
? [
|
? [
|
||||||
InputChip(
|
Center(
|
||||||
label: Text(provider.tag!),
|
child: InputChip(
|
||||||
backgroundColor: const Color(0xFF090909),
|
label: Text(mediaState.tag!),
|
||||||
labelStyle: const TextStyle(color: Colors.white),
|
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
provider.setTag(null);
|
mediaNotifier.setTag(null);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMediaItem(MediaItem item, bool isActive) {
|
Widget _buildMediaItem(MediaItem item, bool isActive) {
|
||||||
final provider = Provider.of<MediaProvider>(context);
|
final mediaNotifier = ref.read(mediaProvider.notifier);
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -197,16 +206,11 @@ class _DetailViewState extends State<DetailView> {
|
|||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
imageUrl: item.mediaUrl,
|
imageUrl: item.mediaUrl,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
placeholder: (context, url) => CircularProgressIndicator(),
|
placeholder: (context, url) => const CircularProgressIndicator(),
|
||||||
errorWidget: (context, url, error) => Icon(Icons.error),
|
errorWidget: (context, url, error) => const Icon(Icons.error),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
VideoWidget(details: item, isActive: isActive),
|
VideoWidget(details: item, isActive: isActive),
|
||||||
/*const SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
'f0ck #${item.id.toString()}',
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 18),
|
|
||||||
),*/
|
|
||||||
const SizedBox(height: 10, width: double.infinity),
|
const SizedBox(height: 10, width: double.infinity),
|
||||||
Wrap(
|
Wrap(
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
@ -216,7 +220,7 @@ class _DetailViewState extends State<DetailView> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (tag.tag == 'sfw' || tag.tag == 'nsfw') return;
|
if (tag.tag == 'sfw' || tag.tag == 'nsfw') return;
|
||||||
setState(() {
|
setState(() {
|
||||||
provider.setTag(tag.tag);
|
mediaNotifier.setTag(tag.tag);
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1,37 +1,29 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/screens/DetailView.dart';
|
import 'package:f0ckapp/screens/DetailView.dart';
|
||||||
import 'package:f0ckapp/providers/MediaProvider.dart';
|
import 'package:f0ckapp/providers/MediaProvider.dart';
|
||||||
import 'package:f0ckapp/utils/AppVersion.dart';
|
import 'package:f0ckapp/utils/AppVersion.dart';
|
||||||
|
import 'package:f0ckapp/providers/ThemeProvider.dart';
|
||||||
|
|
||||||
class MediaGrid extends StatefulWidget {
|
const List<String> mediaTypes = ["alles", "image", "video", "audio"];
|
||||||
|
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
|
||||||
|
|
||||||
|
class MediaGrid extends ConsumerStatefulWidget {
|
||||||
const MediaGrid({super.key});
|
const MediaGrid({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State createState() => _MediaGridState();
|
ConsumerState<MediaGrid> createState() => _MediaGridState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MediaGridState extends State<MediaGrid> {
|
class _MediaGridState extends ConsumerState<MediaGrid> {
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
@override
|
final TextEditingController _usernameController = TextEditingController();
|
||||||
void initState() {
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
super.initState();
|
|
||||||
|
|
||||||
final provider = Provider.of<MediaProvider>(context, listen: false);
|
|
||||||
|
|
||||||
Future.microtask(() {
|
|
||||||
provider.loadMedia();
|
|
||||||
});
|
|
||||||
|
|
||||||
_scrollController.addListener(() {
|
|
||||||
if (_scrollController.position.pixels >=
|
|
||||||
_scrollController.position.maxScrollExtent - 200) {
|
|
||||||
provider.loadMedia(notify: false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
int _calculateCrossAxisCount(BuildContext context, int defaultCount) {
|
int _calculateCrossAxisCount(BuildContext context, int defaultCount) {
|
||||||
return defaultCount == 0
|
return defaultCount == 0
|
||||||
@ -39,30 +31,66 @@ class _MediaGridState extends State<MediaGrid> {
|
|||||||
: defaultCount;
|
: defaultCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
Future.microtask(() {
|
||||||
|
ref.read(mediaProvider.notifier).loadMedia();
|
||||||
|
});
|
||||||
|
_scrollController.addListener(() {
|
||||||
|
if (_scrollController.position.pixels >=
|
||||||
|
_scrollController.position.maxScrollExtent - 200) {
|
||||||
|
ref.read(mediaProvider.notifier).loadMedia();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
_usernameController.dispose();
|
||||||
|
_passwordController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = Provider.of<MediaProvider>(context);
|
final mediaState = ref.watch(mediaProvider);
|
||||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
final mediaNotifier = ref.read(mediaProvider.notifier);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: scaffoldKey,
|
key: _scaffoldKey,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
//centerTitle: true,
|
title: GestureDetector(
|
||||||
title: Text('fApp v${AppVersion.version}'),
|
child: Row(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/f0ck_small.webp',
|
||||||
|
fit: BoxFit.fitHeight,
|
||||||
|
),
|
||||||
|
Text('fApp', style: TextStyle(fontSize: 24)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
mediaNotifier.setTag(null);
|
||||||
|
_scrollController.jumpTo(0);
|
||||||
|
},
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
provider.random ? Icons.shuffle_on_outlined : Icons.shuffle,
|
mediaState.random ? Icons.shuffle_on_outlined : Icons.shuffle,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
provider.toggleRandom();
|
mediaNotifier.toggleRandom();
|
||||||
_scrollController.jumpTo(0);
|
_scrollController.jumpTo(0);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.menu),
|
icon: const Icon(Icons.menu),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
scaffoldKey.currentState?.openEndDrawer();
|
_scaffoldKey.currentState?.openEndDrawer();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -72,32 +100,11 @@ class _MediaGridState extends State<MediaGrid> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
Text('type: '),
|
const Text('type: '),
|
||||||
DropdownButton<String>(
|
DropdownButton<String>(
|
||||||
// type
|
value: mediaTypes[mediaState.typeIndex],
|
||||||
value: provider.type,
|
|
||||||
isDense: true,
|
isDense: true,
|
||||||
//icon: SizedBox.shrink(),
|
items: mediaTypes.map((String value) {
|
||||||
items: provider.types.map((String value) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: value,
|
|
||||||
child: Text(value, style: TextStyle(color: Colors.white)),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (String? newValue) {
|
|
||||||
if (newValue != null) {
|
|
||||||
provider.setType(newValue);
|
|
||||||
_scrollController.jumpTo(0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Text('mode: '),
|
|
||||||
DropdownButton<String>(
|
|
||||||
// mode
|
|
||||||
value: provider.modes[provider.mode],
|
|
||||||
isDense: true,
|
|
||||||
//icon: SizedBox.shrink(),
|
|
||||||
items: provider.modes.map((String value) {
|
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: value,
|
value: value,
|
||||||
child: Text(value),
|
child: Text(value),
|
||||||
@ -105,7 +112,24 @@ class _MediaGridState extends State<MediaGrid> {
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
onChanged: (String? newValue) {
|
onChanged: (String? newValue) {
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
provider.setMode(provider.modes.indexOf(newValue));
|
mediaNotifier.setType(newValue);
|
||||||
|
_scrollController.jumpTo(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Text('mode: '),
|
||||||
|
DropdownButton<String>(
|
||||||
|
value: mediaModes[mediaState.modeIndex],
|
||||||
|
isDense: true,
|
||||||
|
items: mediaModes.map((String value) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: value,
|
||||||
|
child: Text(value),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
if (newValue != null) {
|
||||||
|
mediaNotifier.setMode(mediaModes.indexOf(newValue));
|
||||||
_scrollController.jumpTo(0);
|
_scrollController.jumpTo(0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -118,7 +142,7 @@ class _MediaGridState extends State<MediaGrid> {
|
|||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
children: [
|
children: [
|
||||||
DrawerHeader(
|
DrawerHeader(
|
||||||
decoration: BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: AssetImage('assets/images/menu.webp'),
|
image: AssetImage('assets/images/menu.webp'),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@ -127,63 +151,138 @@ class _MediaGridState extends State<MediaGrid> {
|
|||||||
),
|
),
|
||||||
child: null,
|
child: null,
|
||||||
),
|
),
|
||||||
|
ExpansionTile(
|
||||||
|
title: const Text('Login'),
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
readOnly: true,
|
||||||
|
controller: _usernameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Benutzername',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextField(
|
||||||
|
readOnly: true,
|
||||||
|
controller: _passwordController,
|
||||||
|
obscureText: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Passwort',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text("noch nicht implementiert lol"),
|
||||||
|
),
|
||||||
|
/*final success = await login(
|
||||||
|
_usernameController.text,
|
||||||
|
_passwordController.text,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Login fehlgeschlagen!")),
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('Login'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ExpansionTile(
|
||||||
|
title: const Text('Theme'),
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
children: themes.map((themeData) {
|
||||||
|
final currentTheme = ref.watch(themeNotifierProvider);
|
||||||
|
final isSelected = currentTheme == themeData.theme;
|
||||||
|
return ListTile(
|
||||||
|
title: Text(themeData.name),
|
||||||
|
selected: isSelected,
|
||||||
|
selectedTileColor: Colors.blue.withValues(alpha: 0.2),
|
||||||
|
onTap: () async {
|
||||||
|
await ref
|
||||||
|
.read(themeNotifierProvider.notifier)
|
||||||
|
.updateTheme(themeData.name);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('v${AppVersion.version}'),
|
title: Text('v${AppVersion.version}'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('jooong lass das, hier ist nichts')),
|
const SnackBar(
|
||||||
|
content: Text('jooong lass das, hier ist nichts'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
persistentFooterButtons: provider.tag != null
|
persistentFooterButtons: mediaState.tag != null
|
||||||
? [
|
? [
|
||||||
InputChip(
|
Center(
|
||||||
label: Text(provider.tag!),
|
child: InputChip(
|
||||||
backgroundColor: const Color(0xFF090909),
|
label: Text(mediaState.tag!),
|
||||||
labelStyle: const TextStyle(color: Colors.white),
|
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
provider.setTag(null);
|
mediaNotifier.setTag(null);
|
||||||
_scrollController.jumpTo(0);
|
_scrollController.jumpTo(0);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await provider.resetMedia();
|
mediaNotifier.resetMedia();
|
||||||
_scrollController.jumpTo(0);
|
_scrollController.jumpTo(0);
|
||||||
},
|
},
|
||||||
child: Consumer<MediaProvider>(
|
child: GridView.builder(
|
||||||
builder: (context, mediaProvider, child) {
|
key: const PageStorageKey('mediaGrid'),
|
||||||
return GridView.builder(
|
|
||||||
key: PageStorageKey('mediaGrid'),
|
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: _calculateCrossAxisCount(
|
crossAxisCount: _calculateCrossAxisCount(
|
||||||
context,
|
context,
|
||||||
provider.crossAxisCount,
|
mediaState.crossAxisCount,
|
||||||
),
|
),
|
||||||
crossAxisSpacing: 5.0,
|
crossAxisSpacing: 5.0,
|
||||||
mainAxisSpacing: 5.0,
|
mainAxisSpacing: 5.0,
|
||||||
),
|
),
|
||||||
itemCount:
|
itemCount:
|
||||||
provider.mediaItems.length + (provider.isLoading ? 1 : 0),
|
mediaState.mediaItems.length + (mediaState.isLoading ? 1 : 0),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index >= provider.mediaItems.length) {
|
if (index >= mediaState.mediaItems.length) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
final item = provider.mediaItems[index];
|
final item = mediaState.mediaItems[index];
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
bool? ret = await Navigator.push(
|
bool? ret = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) => DetailView(initialItemId: item.id),
|
||||||
DetailView(initialItemId: item.id),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (ret != null && ret) {
|
if (ret != null && ret) {
|
||||||
@ -196,11 +295,12 @@ class _MediaGridState extends State<MediaGrid> {
|
|||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
imageUrl: item.thumbnailUrl,
|
imageUrl: item.thumbnailUrl,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (context, url) => SizedBox.shrink(),
|
placeholder: (context, url) => const SizedBox.shrink(),
|
||||||
errorWidget: (context, url, error) => Icon(Icons.error),
|
errorWidget: (context, url, error) =>
|
||||||
|
const Icon(Icons.error),
|
||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
alignment: FractionalOffset.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.square,
|
Icons.square,
|
||||||
color: switch (item.mode) {
|
color: switch (item.mode) {
|
||||||
@ -215,8 +315,6 @@ class _MediaGridState extends State<MediaGrid> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/models/MediaItem.dart';
|
import 'package:f0ckapp/models/MediaItem.dart';
|
||||||
|
|
||||||
|
final storage = FlutterSecureStorage();
|
||||||
|
|
||||||
Future<List<MediaItem>> fetchMedia({
|
Future<List<MediaItem>> fetchMedia({
|
||||||
int? older,
|
int? older,
|
||||||
String? type,
|
String? type,
|
||||||
@ -43,3 +47,23 @@ Future<MediaItem> fetchMediaDetail(int itemId) async {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> login(String username, String password) async {
|
||||||
|
final Uri url = Uri.parse('https://api.f0ck.me/login');
|
||||||
|
|
||||||
|
final response = await http.post(
|
||||||
|
url,
|
||||||
|
body: {'username': username, 'password': password},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = jsonDecode(response.body);
|
||||||
|
final token = data['token'];
|
||||||
|
|
||||||
|
await storage.write(key: "token", value: token);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:cached_video_player_plus/cached_video_player_plus.dart';
|
import 'package:cached_video_player_plus/cached_video_player_plus.dart';
|
||||||
|
|
||||||
class VideoControlsOverlay extends StatelessWidget {
|
import 'package:f0ckapp/providers/MediaProvider.dart';
|
||||||
|
|
||||||
|
class VideoControlsOverlay extends ConsumerWidget {
|
||||||
final CachedVideoPlayerPlusController controller;
|
final CachedVideoPlayerPlusController controller;
|
||||||
final VoidCallback button;
|
final VoidCallback button;
|
||||||
|
|
||||||
@ -12,10 +16,32 @@ class VideoControlsOverlay extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
final mediaState = ref.watch(mediaProvider);
|
||||||
|
final mediaNotifier = ref.read(mediaProvider.notifier);
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
Positioned(
|
||||||
|
right: 12,
|
||||||
|
top: 12,
|
||||||
|
child: _ControlButton(
|
||||||
|
mediaState.muted ? Icons.volume_off : Icons.volume_up,
|
||||||
|
() {
|
||||||
|
button();
|
||||||
|
mediaNotifier.toggleMute();
|
||||||
|
},
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 12,
|
||||||
|
bottom: 12,
|
||||||
|
child: _ControlButton(Icons.fullscreen, () {
|
||||||
|
button();
|
||||||
|
}, size: 16),
|
||||||
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:cached_video_player_plus/cached_video_player_plus.dart';
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
|
||||||
import 'package:f0ckapp/models/MediaItem.dart';
|
|
||||||
import 'package:f0ckapp/widgets/VideoOverlay.dart';
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
class VideoWidget extends StatefulWidget {
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:cached_video_player_plus/cached_video_player_plus.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:f0ckapp/models/MediaItem.dart';
|
||||||
|
import 'package:f0ckapp/widgets/VideoOverlay.dart';
|
||||||
|
import 'package:f0ckapp/providers/MediaProvider.dart';
|
||||||
|
|
||||||
|
class VideoWidget extends ConsumerStatefulWidget {
|
||||||
final MediaItem details;
|
final MediaItem details;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
|
||||||
const VideoWidget({super.key, required this.details, required this.isActive});
|
const VideoWidget({super.key, required this.details, required this.isActive});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State createState() => _VideoWidgetState();
|
ConsumerState<VideoWidget> createState() => _VideoWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VideoWidgetState extends State<VideoWidget> {
|
class _VideoWidgetState extends ConsumerState<VideoWidget> {
|
||||||
late CachedVideoPlayerPlusController _controller;
|
late CachedVideoPlayerPlusController _controller;
|
||||||
bool _showControls = false;
|
bool _showControls = false;
|
||||||
Timer? _hideControlsTimer;
|
Timer? _hideControlsTimer;
|
||||||
@ -34,10 +39,26 @@ class _VideoWidgetState extends State<VideoWidget> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
||||||
_controller.addListener(() => setState(() {}));
|
_controller.addListener(() => setState(() {}));
|
||||||
|
|
||||||
if (widget.isActive) {
|
if (widget.isActive) {
|
||||||
_controller.play();
|
_controller.play();
|
||||||
}
|
}
|
||||||
_controller.setLooping(true);
|
_controller.setLooping(true);
|
||||||
|
|
||||||
|
final muted = ref.read(mediaProvider).muted;
|
||||||
|
_controller.setVolume(muted ? 0.0 : 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant VideoWidget oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.isActive != oldWidget.isActive) {
|
||||||
|
if (widget.isActive) {
|
||||||
|
_controller.play();
|
||||||
|
} else {
|
||||||
|
_controller.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -47,24 +68,13 @@ class _VideoWidgetState extends State<VideoWidget> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(VideoWidget oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (widget.isActive) {
|
|
||||||
_controller.play();
|
|
||||||
} else {
|
|
||||||
_controller.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onTap({bool ctrlButton = false}) {
|
void _onTap({bool ctrlButton = false}) {
|
||||||
if (!ctrlButton) {
|
if (!ctrlButton) {
|
||||||
setState(() => _showControls = !_showControls);
|
setState(() => _showControls = !_showControls);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_showControls) {
|
if (_showControls) {
|
||||||
_hideControlsTimer?.cancel();
|
_hideControlsTimer?.cancel();
|
||||||
_hideControlsTimer = Timer(Duration(seconds: 2), () {
|
_hideControlsTimer = Timer(const Duration(seconds: 2), () {
|
||||||
setState(() => _showControls = false);
|
setState(() => _showControls = false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -72,6 +82,12 @@ class _VideoWidgetState extends State<VideoWidget> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final muted = ref.watch(mediaProvider).muted;
|
||||||
|
if (_controller.value.isInitialized &&
|
||||||
|
_controller.value.volume != (muted ? 0.0 : 1.0)) {
|
||||||
|
_controller.setVolume(muted ? 0.0 : 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
bool isAudio = widget.details.mime.startsWith('audio');
|
bool isAudio = widget.details.mime.startsWith('audio');
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@ -91,7 +107,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
|||||||
imageUrl: widget.details.coverUrl,
|
imageUrl: widget.details.coverUrl,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (context, url) =>
|
placeholder: (context, url) =>
|
||||||
CircularProgressIndicator(),
|
const CircularProgressIndicator(),
|
||||||
errorWidget: (context, url, error) => Image.asset(
|
errorWidget: (context, url, error) => Image.asset(
|
||||||
'assets/images/music.webp',
|
'assets/images/music.webp',
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
@ -100,7 +116,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
|||||||
)
|
)
|
||||||
: _controller.value.isInitialized
|
: _controller.value.isInitialized
|
||||||
? CachedVideoPlayerPlus(_controller)
|
? CachedVideoPlayerPlus(_controller)
|
||||||
: Center(child: CircularProgressIndicator()),
|
: const Center(child: CircularProgressIndicator()),
|
||||||
),
|
),
|
||||||
if (_controller.value.isInitialized && _showControls) ...[
|
if (_controller.value.isInitialized && _showControls) ...[
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
|
1
linux/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
flutter/ephemeral
|
|
@ -1,128 +0,0 @@
|
|||||||
# Project-level configuration.
|
|
||||||
cmake_minimum_required(VERSION 3.13)
|
|
||||||
project(runner LANGUAGES CXX)
|
|
||||||
|
|
||||||
# The name of the executable created for the application. Change this to change
|
|
||||||
# the on-disk name of your application.
|
|
||||||
set(BINARY_NAME "f0ckapp")
|
|
||||||
# The unique GTK application identifier for this application. See:
|
|
||||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
|
||||||
set(APPLICATION_ID "com.example.f0ckapp")
|
|
||||||
|
|
||||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
|
||||||
# versions of CMake.
|
|
||||||
cmake_policy(SET CMP0063 NEW)
|
|
||||||
|
|
||||||
# Load bundled libraries from the lib/ directory relative to the binary.
|
|
||||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
|
||||||
|
|
||||||
# Root filesystem for cross-building.
|
|
||||||
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
|
||||||
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
|
||||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Define build configuration options.
|
|
||||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|
||||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
|
||||||
STRING "Flutter build mode" FORCE)
|
|
||||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
|
||||||
"Debug" "Profile" "Release")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Compilation settings that should be applied to most targets.
|
|
||||||
#
|
|
||||||
# Be cautious about adding new options here, as plugins use this function by
|
|
||||||
# default. In most cases, you should add new options to specific targets instead
|
|
||||||
# of modifying this function.
|
|
||||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
|
||||||
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
|
||||||
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
|
||||||
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
|
||||||
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# Flutter library and tool build rules.
|
|
||||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
|
||||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
|
||||||
|
|
||||||
# System-level dependencies.
|
|
||||||
find_package(PkgConfig REQUIRED)
|
|
||||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
|
||||||
|
|
||||||
# Application build; see runner/CMakeLists.txt.
|
|
||||||
add_subdirectory("runner")
|
|
||||||
|
|
||||||
# Run the Flutter tool portions of the build. This must not be removed.
|
|
||||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
|
||||||
|
|
||||||
# Only the install-generated bundle's copy of the executable will launch
|
|
||||||
# correctly, since the resources must in the right relative locations. To avoid
|
|
||||||
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
|
||||||
# the default top-level location.
|
|
||||||
set_target_properties(${BINARY_NAME}
|
|
||||||
PROPERTIES
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Generated plugin build rules, which manage building the plugins and adding
|
|
||||||
# them to the application.
|
|
||||||
include(flutter/generated_plugins.cmake)
|
|
||||||
|
|
||||||
|
|
||||||
# === Installation ===
|
|
||||||
# By default, "installing" just makes a relocatable bundle in the build
|
|
||||||
# directory.
|
|
||||||
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
|
||||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
|
||||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Start with a clean build bundle directory every time.
|
|
||||||
install(CODE "
|
|
||||||
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
|
||||||
" COMPONENT Runtime)
|
|
||||||
|
|
||||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
|
||||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
|
||||||
|
|
||||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
|
||||||
COMPONENT Runtime)
|
|
||||||
|
|
||||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
|
||||||
COMPONENT Runtime)
|
|
||||||
|
|
||||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
|
||||||
COMPONENT Runtime)
|
|
||||||
|
|
||||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
|
||||||
install(FILES "${bundled_library}"
|
|
||||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
|
||||||
COMPONENT Runtime)
|
|
||||||
endforeach(bundled_library)
|
|
||||||
|
|
||||||
# Copy the native assets provided by the build.dart from all packages.
|
|
||||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
|
|
||||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
|
||||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
|
||||||
COMPONENT Runtime)
|
|
||||||
|
|
||||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
|
||||||
# from a previous install.
|
|
||||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
|
||||||
install(CODE "
|
|
||||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
|
||||||
" COMPONENT Runtime)
|
|
||||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
|
||||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
|
||||||
|
|
||||||
# Install the AOT library on non-Debug builds only.
|
|
||||||
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
|
||||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
|
||||||
COMPONENT Runtime)
|
|
||||||
endif()
|
|
@ -1,88 +0,0 @@
|
|||||||
# This file controls Flutter-level build steps. It should not be edited.
|
|
||||||
cmake_minimum_required(VERSION 3.10)
|
|
||||||
|
|
||||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
|
||||||
|
|
||||||
# Configuration provided via flutter tool.
|
|
||||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
|
||||||
|
|
||||||
# TODO: Move the rest of this into files in ephemeral. See
|
|
||||||
# https://github.com/flutter/flutter/issues/57146.
|
|
||||||
|
|
||||||
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
|
||||||
# which isn't available in 3.10.
|
|
||||||
function(list_prepend LIST_NAME PREFIX)
|
|
||||||
set(NEW_LIST "")
|
|
||||||
foreach(element ${${LIST_NAME}})
|
|
||||||
list(APPEND NEW_LIST "${PREFIX}${element}")
|
|
||||||
endforeach(element)
|
|
||||||
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# === Flutter Library ===
|
|
||||||
# System-level dependencies.
|
|
||||||
find_package(PkgConfig REQUIRED)
|
|
||||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
|
||||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
|
||||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
|
||||||
|
|
||||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
|
||||||
|
|
||||||
# Published to parent scope for install step.
|
|
||||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
|
||||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
|
||||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
|
||||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
|
||||||
|
|
||||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
|
||||||
"fl_basic_message_channel.h"
|
|
||||||
"fl_binary_codec.h"
|
|
||||||
"fl_binary_messenger.h"
|
|
||||||
"fl_dart_project.h"
|
|
||||||
"fl_engine.h"
|
|
||||||
"fl_json_message_codec.h"
|
|
||||||
"fl_json_method_codec.h"
|
|
||||||
"fl_message_codec.h"
|
|
||||||
"fl_method_call.h"
|
|
||||||
"fl_method_channel.h"
|
|
||||||
"fl_method_codec.h"
|
|
||||||
"fl_method_response.h"
|
|
||||||
"fl_plugin_registrar.h"
|
|
||||||
"fl_plugin_registry.h"
|
|
||||||
"fl_standard_message_codec.h"
|
|
||||||
"fl_standard_method_codec.h"
|
|
||||||
"fl_string_codec.h"
|
|
||||||
"fl_value.h"
|
|
||||||
"fl_view.h"
|
|
||||||
"flutter_linux.h"
|
|
||||||
)
|
|
||||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
|
||||||
add_library(flutter INTERFACE)
|
|
||||||
target_include_directories(flutter INTERFACE
|
|
||||||
"${EPHEMERAL_DIR}"
|
|
||||||
)
|
|
||||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
|
||||||
target_link_libraries(flutter INTERFACE
|
|
||||||
PkgConfig::GTK
|
|
||||||
PkgConfig::GLIB
|
|
||||||
PkgConfig::GIO
|
|
||||||
)
|
|
||||||
add_dependencies(flutter flutter_assemble)
|
|
||||||
|
|
||||||
# === Flutter tool backend ===
|
|
||||||
# _phony_ is a non-existent file to force this command to run every time,
|
|
||||||
# since currently there's no way to get a full input/output list from the
|
|
||||||
# flutter tool.
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E env
|
|
||||||
${FLUTTER_TOOL_ENVIRONMENT}
|
|
||||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
|
||||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
add_custom_target(flutter_assemble DEPENDS
|
|
||||||
"${FLUTTER_LIBRARY}"
|
|
||||||
${FLUTTER_LIBRARY_HEADERS}
|
|
||||||
)
|
|