Compare commits
8 Commits
v1.1.5+35
...
v1.1.10+40
Author | SHA1 | Date | |
---|---|---|---|
e945844151 | |||
74eb6e3d26 | |||
9755066d1e | |||
671b3cfbe0 | |||
93fb3536ee | |||
346e447d5e | |||
f7777821fd | |||
ffbde73300 |
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 f0ck
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
52
README.md
@ -1,16 +1,48 @@
|
|||||||
# f0ckapp
|
# fApp
|
||||||
|

|
||||||
|
## Overview
|
||||||
|
|
||||||
A new Flutter project.
|
fApp is the mobile application for the website [f0ck.me](https://f0ck.me). This app provides a user-friendly interface to access the content of the website and utilize its features conveniently from your mobile device.
|
||||||
|
|
||||||
## Getting Started
|
## Installation
|
||||||
|
|
||||||
This project is a starting point for a Flutter application.
|
fApp is available in its own F-Droid repository.
|
||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
### Installation Steps
|
||||||
|
|
||||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
1. Add the F-Droid repository to your F-Droid app:
|
||||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
- Go to the settings in the F-Droid app.
|
||||||
|
- Select "Repositories" and add the URL `https://fdroid.flumm.io/fdroid/repo/`.
|
||||||
|
|
||||||
For help getting started with Flutter development, view the
|
2. Search for "f0ckapp" in the F-Droid app and install the app.
|
||||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
|
||||||
samples, guidance on mobile development, and a full API reference.
|
## Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Flutter SDK
|
||||||
|
- Dart SDK
|
||||||
|
- Android Studio or Visual Studio Code
|
||||||
|
|
||||||
|
### Setting Up the Project
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://git.lat/f0ck/fApp.git
|
||||||
|
cd fApp
|
||||||
|
2. Install dependencies:
|
||||||
|
```flutter pub get```
|
||||||
|
3. Run the app:
|
||||||
|
```flutter run```
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
don't.
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License. See the LICENSE file for more information.
|
||||||
|
|
||||||
|
### Contact
|
||||||
|
|
||||||
|
For questions or feedback, you can reach us at [contact@f0ck.me](mailto:contact@f0ck.me).
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="f0ckapp"
|
android:label="f0ckapp"
|
||||||
@ -28,12 +30,26 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data android:name="flutter_deeplinking_enabled" android:value="true"/>
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
<data android:scheme="https" android:host="f0ck.me"/>
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
<data android:scheme="f0ck" android:host="com.f0ck.f0ckapp"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 327 KiB After Width: | Height: | Size: 345 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -1,33 +1,89 @@
|
|||||||
|
import 'package:f0ckapp/providers/media_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/screens/MediaGrid.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:f0ckapp/utils/AppVersion.dart';
|
|
||||||
import 'package:f0ckapp/providers/ThemeProvider.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||||
|
|
||||||
|
import 'package:f0ckapp/screens/mediagrid_screen.dart';
|
||||||
|
import 'package:f0ckapp/screens/detailview_screen.dart';
|
||||||
|
import 'package:f0ckapp/utils/appversion_util.dart';
|
||||||
|
import 'package:f0ckapp/providers/theme_provider.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
|
await FlutterDownloader.initialize();
|
||||||
await AppVersion.init();
|
await AppVersion.init();
|
||||||
|
|
||||||
runApp(ProviderScope(child: const F0ckApp()));
|
runApp(ProviderScope(child: F0ckApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
class F0ckApp extends ConsumerWidget {
|
class F0ckApp extends ConsumerWidget {
|
||||||
const F0ckApp({super.key});
|
F0ckApp({super.key});
|
||||||
|
|
||||||
|
final GoRouter _router = GoRouter(
|
||||||
|
initialLocation: '/',
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
builder: (context, state) {
|
||||||
|
return const MediaGrid();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/:rest(.*)',
|
||||||
|
builder: (context, state) {
|
||||||
|
final isInternalLink = (state.extra is bool && state.extra == true);
|
||||||
|
final fullPath = state.matchedLocation;
|
||||||
|
|
||||||
|
final regExp = RegExp(
|
||||||
|
r'^(?:/tag/(?<tag>.+?))?(?:/(?<mime>image|audio|video))?(?:/(?<itemid>\d+))?$',
|
||||||
|
);
|
||||||
|
final match = regExp.firstMatch(fullPath);
|
||||||
|
|
||||||
|
if (match == null) {
|
||||||
|
return const Scaffold(body: Center(child: Text('Ungültiger Link')));
|
||||||
|
}
|
||||||
|
|
||||||
|
final String? tag = match.namedGroup('tag');
|
||||||
|
final String? mime = match.namedGroup('mime');
|
||||||
|
final String? idStr = match.namedGroup('itemid');
|
||||||
|
final int? itemId = idStr != null ? int.tryParse(idStr) : null;
|
||||||
|
const preloadOffset = 50;
|
||||||
|
|
||||||
|
return Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
final mediaNotifier = ref.read(mediaProvider.notifier);
|
||||||
|
if (!isInternalLink) {
|
||||||
|
mediaNotifier.setType(mime ?? "alles");
|
||||||
|
mediaNotifier.setTag(tag);
|
||||||
|
}
|
||||||
|
if (itemId != null) {
|
||||||
|
await mediaNotifier.loadMedia(id: itemId + preloadOffset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (itemId != null) {
|
||||||
|
return DetailView(initialItemId: itemId);
|
||||||
|
} else {
|
||||||
|
return MediaGrid();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Consumer(
|
final theme = ref.watch(themeNotifierProvider);
|
||||||
builder: (context, ref, _) {
|
return MaterialApp.router(
|
||||||
return MaterialApp(
|
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ref.watch(themeNotifierProvider),
|
routerConfig: _router,
|
||||||
home: const MediaGrid(),
|
theme: theme,
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/models/MediaItem.dart';
|
import 'package:f0ckapp/models/mediaitem_model.dart';
|
||||||
import 'package:f0ckapp/services/Api.dart';
|
import 'package:f0ckapp/services/api_service.dart';
|
||||||
|
|
||||||
const List<String> mediaTypes = ["alles", "image", "video", "audio"];
|
const List<String> mediaTypes = ["alles", "image", "video", "audio"];
|
||||||
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
|
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
|
||||||
@ -106,14 +106,12 @@ class MediaNotifier extends StateNotifier<MediaState> {
|
|||||||
state = state.replace(mediaItems: updated);
|
state = state.replace(mediaItems: updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadMedia() async {
|
Future<void> loadMedia({int? id}) async {
|
||||||
if (state.isLoading) return;
|
if (state.isLoading) return;
|
||||||
|
|
||||||
state = state.replace(isLoading: true);
|
state = state.replace(isLoading: true);
|
||||||
try {
|
try {
|
||||||
final older = state.mediaItems.isNotEmpty
|
final older =
|
||||||
? state.mediaItems.last.id
|
id ?? (state.mediaItems.isNotEmpty ? state.mediaItems.last.id : null);
|
||||||
: null;
|
|
||||||
final newMedia = await fetchMedia(
|
final newMedia = await fetchMedia(
|
||||||
older: older,
|
older: older,
|
||||||
type: mediaTypes[state.typeIndex],
|
type: mediaTypes[state.typeIndex],
|
@ -3,6 +3,17 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
final Map<String, ThemeData> themeMap = {
|
||||||
|
'f0ck': f0ckTheme,
|
||||||
|
'P1nk': p1nkTheme, // done
|
||||||
|
'Orange': orangeTheme,
|
||||||
|
'Amoled': amoledTheme,
|
||||||
|
'Paper': paperTheme,
|
||||||
|
//'Iced': icedTheme,
|
||||||
|
'f0ck95': f0ck95Theme,
|
||||||
|
'f0ck95d': f0ck95dTheme,
|
||||||
|
};
|
||||||
|
|
||||||
class ThemeNotifier extends StateNotifier<ThemeData> {
|
class ThemeNotifier extends StateNotifier<ThemeData> {
|
||||||
final FlutterSecureStorage secureStorage;
|
final FlutterSecureStorage secureStorage;
|
||||||
|
|
||||||
@ -11,23 +22,24 @@ class ThemeNotifier extends StateNotifier<ThemeData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadTheme() async {
|
Future<void> _loadTheme() async {
|
||||||
|
try {
|
||||||
String? savedThemeName = await secureStorage.read(key: 'theme');
|
String? savedThemeName = await secureStorage.read(key: 'theme');
|
||||||
if (savedThemeName != null) {
|
if (savedThemeName != null && themeMap.containsKey(savedThemeName)) {
|
||||||
final customTheme = themes.firstWhere(
|
state = themeMap[savedThemeName]!;
|
||||||
(t) => t.name == savedThemeName,
|
}
|
||||||
orElse: () => CustomTheme(name: 'f0ck', theme: f0ckTheme),
|
} catch (error) {
|
||||||
);
|
debugPrint('Fehler beim Laden des Themes: $error');
|
||||||
state = customTheme.theme;
|
state = f0ckTheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateTheme(String themeName) async {
|
Future<void> updateTheme(String themeName) async {
|
||||||
|
try {
|
||||||
await secureStorage.write(key: 'theme', value: themeName);
|
await secureStorage.write(key: 'theme', value: themeName);
|
||||||
final newTheme = themes.firstWhere(
|
state = themeMap[themeName] ?? f0ckTheme;
|
||||||
(t) => t.name == themeName,
|
} catch (error) {
|
||||||
orElse: () => CustomTheme(name: 'f0ck', theme: f0ckTheme),
|
debugPrint('Fehler beim Aktualisieren des Themes: $error');
|
||||||
);
|
}
|
||||||
state = newTheme.theme;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,28 +47,12 @@ final themeNotifierProvider = StateNotifierProvider<ThemeNotifier, ThemeData>((
|
|||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
return ThemeNotifier(
|
return ThemeNotifier(
|
||||||
secureStorage: FlutterSecureStorage(
|
secureStorage: const FlutterSecureStorage(
|
||||||
aOptions: AndroidOptions(encryptedSharedPreferences: true),
|
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: const Color(0xFF9FFF00),
|
primaryColor: const Color(0xFF9FFF00),
|
||||||
@ -78,13 +74,53 @@ final ThemeData f0ckTheme = ThemeData(
|
|||||||
bodyLarge: TextStyle(color: Colors.white),
|
bodyLarge: TextStyle(color: Colors.white),
|
||||||
bodyMedium: TextStyle(color: Colors.white),
|
bodyMedium: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
buttonColor: const Color(0xFF9FFF00),
|
style: ElevatedButton.styleFrom(
|
||||||
textTheme: ButtonTextTheme.primary,
|
foregroundColor: const Color(0xFF000000),
|
||||||
|
backgroundColor: const Color(0xFF9FFF00),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
scrollbarTheme: ScrollbarThemeData(
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
thumbColor: WidgetStatePropertyAll<Color>(Color(0xFF2B2B2B)),
|
thumbColor: WidgetStateProperty.all<Color>(const Color(0xFF2B2B2B)),
|
||||||
trackColor: WidgetStatePropertyAll<Color>(Color(0xFF424242)),
|
trackColor: WidgetStateProperty.all<Color>(const Color(0xFF424242)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final ThemeData p1nkTheme = ThemeData(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
primaryColor: const Color(0xFF171717),
|
||||||
|
scaffoldBackgroundColor: const Color(0xFF171717),
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
color: Color(0xFF2B2B2B),
|
||||||
|
foregroundColor: Color(0xFFFF00D0),
|
||||||
|
elevation: 2,
|
||||||
|
),
|
||||||
|
textTheme: const TextTheme(
|
||||||
|
bodyLarge: TextStyle(color: Color(0xFFFFFFFF)),
|
||||||
|
bodyMedium: TextStyle(color: Color(0xFFFFFFFF)),
|
||||||
|
titleLarge: TextStyle(color: Color(0xFFFFFFFF)),
|
||||||
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
foregroundColor: const Color(0xFF000000),
|
||||||
|
backgroundColor: const Color(0xFFFF00D0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
progressIndicatorTheme: const ProgressIndicatorThemeData(
|
||||||
|
color: Color(0xFFFF00D0),
|
||||||
|
),
|
||||||
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
|
thumbColor: WidgetStateProperty.all(const Color(0xFF2B2B2B)),
|
||||||
|
trackColor: WidgetStateProperty.all(const Color(0xFF424242)),
|
||||||
|
),
|
||||||
|
colorScheme: const ColorScheme.dark(
|
||||||
|
primary: Color(0xFF171717),
|
||||||
|
secondary: Color(0xFFFF00D0),
|
||||||
|
surface: Color(0xFF171717),
|
||||||
|
onPrimary: Color(0xFFFFFFFF),
|
||||||
|
onSecondary: Color(0xFF000000),
|
||||||
|
onSurface: Color(0xFFFFFFFF),
|
||||||
|
error: Color(0xFFA72828),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -109,9 +145,11 @@ final ThemeData paperTheme = ThemeData(
|
|||||||
bodyLarge: TextStyle(color: Color(0xFF000000)),
|
bodyLarge: TextStyle(color: Color(0xFF000000)),
|
||||||
bodyMedium: TextStyle(color: Color(0xFF000000)),
|
bodyMedium: TextStyle(color: Color(0xFF000000)),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
buttonColor: const Color(0xFF000000),
|
style: ElevatedButton.styleFrom(
|
||||||
textTheme: ButtonTextTheme.primary,
|
foregroundColor: const Color(0xFFFFFFFF),
|
||||||
|
backgroundColor: const Color(0xFF000000),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -136,13 +174,15 @@ final ThemeData orangeTheme = ThemeData(
|
|||||||
bodyLarge: TextStyle(color: Colors.white),
|
bodyLarge: TextStyle(color: Colors.white),
|
||||||
bodyMedium: TextStyle(color: Colors.white),
|
bodyMedium: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
buttonColor: const Color(0xFFFF6F00),
|
style: ElevatedButton.styleFrom(
|
||||||
textTheme: ButtonTextTheme.primary,
|
foregroundColor: Colors.white,
|
||||||
|
backgroundColor: const Color(0xFFFF6F00),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
scrollbarTheme: ScrollbarThemeData(
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
thumbColor: WidgetStatePropertyAll<Color>(Color(0xFF2B2B2B)),
|
thumbColor: WidgetStateProperty.all<Color>(const Color(0xFF2B2B2B)),
|
||||||
trackColor: WidgetStatePropertyAll<Color>(Color(0xFF424242)),
|
trackColor: WidgetStateProperty.all<Color>(const Color(0xFF424242)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -169,9 +209,11 @@ final ThemeData amoledTheme = ThemeData(
|
|||||||
bodyLarge: TextStyle(color: Colors.white),
|
bodyLarge: TextStyle(color: Colors.white),
|
||||||
bodyMedium: TextStyle(color: Colors.white),
|
bodyMedium: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
buttonColor: const Color(0xFFFFFFFF),
|
style: ElevatedButton.styleFrom(
|
||||||
textTheme: ButtonTextTheme.primary,
|
foregroundColor: Colors.black,
|
||||||
|
backgroundColor: const Color(0xFFFFFFFF),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
scrollbarTheme: ScrollbarThemeData(
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
thumbColor: WidgetStateProperty.all<Color>(const Color(0xFF1D1C1C)),
|
thumbColor: WidgetStateProperty.all<Color>(const Color(0xFF1D1C1C)),
|
||||||
@ -199,13 +241,15 @@ final ThemeData f0ck95Theme = ThemeData(
|
|||||||
bodyLarge: TextStyle(color: Colors.black),
|
bodyLarge: TextStyle(color: Colors.black),
|
||||||
bodyMedium: TextStyle(color: Colors.black),
|
bodyMedium: TextStyle(color: Colors.black),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
buttonColor: Colors.black,
|
style: ElevatedButton.styleFrom(
|
||||||
textTheme: ButtonTextTheme.primary,
|
foregroundColor: Colors.white,
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
scrollbarTheme: ScrollbarThemeData(
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
thumbColor: WidgetStatePropertyAll<Color>(Color(0xFF2B2B2B)),
|
thumbColor: WidgetStateProperty.all<Color>(const Color(0xFF2B2B2B)),
|
||||||
trackColor: WidgetStatePropertyAll<Color>(Color(0xFF424242)),
|
trackColor: WidgetStateProperty.all<Color>(const Color(0xFF424242)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -229,12 +273,14 @@ final ThemeData f0ck95dTheme = ThemeData(
|
|||||||
bodyLarge: TextStyle(color: Colors.white),
|
bodyLarge: TextStyle(color: Colors.white),
|
||||||
bodyMedium: TextStyle(color: Colors.white),
|
bodyMedium: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
buttonColor: Colors.white,
|
style: ElevatedButton.styleFrom(
|
||||||
textTheme: ButtonTextTheme.primary,
|
foregroundColor: Colors.black,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
scrollbarTheme: ScrollbarThemeData(
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
thumbColor: WidgetStatePropertyAll<Color>(Color(0xFF2B2B2B)),
|
thumbColor: WidgetStateProperty.all<Color>(const Color(0xFF2B2B2B)),
|
||||||
trackColor: WidgetStatePropertyAll<Color>(Color(0xFF424242)),
|
trackColor: WidgetStateProperty.all<Color>(const Color(0xFF424242)),
|
||||||
),
|
),
|
||||||
);
|
);
|
@ -2,17 +2,20 @@ import 'dart:io';
|
|||||||
import 'dart:typed_data';
|
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:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/models/MediaItem.dart';
|
import 'package:f0ckapp/models/mediaitem_model.dart';
|
||||||
import 'package:f0ckapp/widgets/VideoWidget.dart';
|
import 'package:f0ckapp/widgets/video_widget.dart';
|
||||||
import 'package:f0ckapp/utils/SmartRefreshIndicator.dart';
|
import 'package:f0ckapp/utils/smartrefreshindicator_util.dart';
|
||||||
import 'package:f0ckapp/utils/PageTransformer.dart';
|
import 'package:f0ckapp/utils/pagetransformer_util.dart';
|
||||||
import 'package:f0ckapp/providers/MediaProvider.dart';
|
import 'package:f0ckapp/providers/media_provider.dart';
|
||||||
|
|
||||||
class DetailView extends ConsumerStatefulWidget {
|
class DetailView extends ConsumerStatefulWidget {
|
||||||
final int initialItemId;
|
final int initialItemId;
|
||||||
@ -24,41 +27,25 @@ class DetailView extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DetailViewState extends ConsumerState<DetailView> {
|
class _DetailViewState extends ConsumerState<DetailView> {
|
||||||
late PageController _pageController;
|
PageController? _pageController;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
final mediaState = ref.read(mediaProvider);
|
|
||||||
final initialIndex = mediaState.mediaItems.indexWhere(
|
|
||||||
(item) => item.id == widget.initialItemId,
|
|
||||||
);
|
|
||||||
_pageController = PageController(initialPage: initialIndex);
|
|
||||||
_currentIndex = initialIndex;
|
|
||||||
|
|
||||||
_pageController.addListener(() {
|
|
||||||
setState(() => _currentIndex = _pageController.page?.round() ?? 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
_preloadAdjacentMedia(initialIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _preloadAdjacentMedia(int index) async {
|
void _preloadAdjacentMedia(int index) async {
|
||||||
final mediaState = ref.read(mediaProvider);
|
final mediaState = ref.read(mediaProvider);
|
||||||
if (index + 1 < mediaState.mediaItems.length) {
|
for (int offset in [-1, 1]) {
|
||||||
final nextUrl = mediaState.mediaItems[index + 1].mediaUrl;
|
final adjacentIndex = index + offset;
|
||||||
if (await DefaultCacheManager().getFileFromCache(nextUrl) == null) {
|
if (adjacentIndex >= 0 && adjacentIndex < mediaState.mediaItems.length) {
|
||||||
await DefaultCacheManager().downloadFile(nextUrl);
|
final url = mediaState.mediaItems[adjacentIndex].mediaUrl;
|
||||||
|
if (await DefaultCacheManager().getFileFromCache(url) == null) {
|
||||||
|
await DefaultCacheManager().downloadFile(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index - 1 >= 0) {
|
|
||||||
final prevUrl = mediaState.mediaItems[index - 1].mediaUrl;
|
|
||||||
if (await DefaultCacheManager().getFileFromCache(prevUrl) == null) {
|
|
||||||
await DefaultCacheManager().downloadFile(prevUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,35 +69,112 @@ class _DetailViewState extends ConsumerState<DetailView> {
|
|||||||
..showSnackBar(SnackBar(content: Text(message)));
|
..showSnackBar(SnackBar(content: Text(message)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _downloadMedia() async {
|
||||||
|
final mediaState = ref.read(mediaProvider);
|
||||||
|
final currentItem = mediaState.mediaItems[_currentIndex];
|
||||||
|
|
||||||
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
|
var status = await Permission.storage.status;
|
||||||
|
if (!status.isGranted) {
|
||||||
|
status = await Permission.storage.request();
|
||||||
|
if (!status.isGranted) {
|
||||||
|
_showError("Speicherberechtigung wurde nicht erteilt.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String localPath;
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
final directory = await getExternalStorageDirectory();
|
||||||
|
localPath = "${directory!.path}/Download/fApp";
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
final directory = await getApplicationDocumentsDirectory();
|
||||||
|
localPath = directory.path;
|
||||||
|
} else {
|
||||||
|
final directory = await getTemporaryDirectory();
|
||||||
|
localPath = directory.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
final savedDir = Directory(localPath);
|
||||||
|
if (!await savedDir.exists()) {
|
||||||
|
await savedDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await FlutterDownloader.enqueue(
|
||||||
|
url: currentItem.mediaUrl,
|
||||||
|
savedDir: localPath,
|
||||||
|
fileName: currentItem.mediaUrl.split('/').last,
|
||||||
|
showNotification: true,
|
||||||
|
openFileFromNotification: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
_showError('Download gestartet: ${currentItem.mediaUrl}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_showError('Download fehlgeschlagen: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_pageController.dispose();
|
_pageController?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final mediaState = ref.watch(mediaProvider);
|
final mediaState = ref.watch(mediaProvider);
|
||||||
final mediaNotifier = ref.read(mediaProvider.notifier);
|
final int itemIndex = mediaState.mediaItems.indexWhere(
|
||||||
|
(item) => item.id == widget.initialItemId,
|
||||||
|
);
|
||||||
|
print('itemIndex: ${itemIndex}; initial: ${widget.initialItemId}');
|
||||||
|
|
||||||
if (mediaState.mediaItems.isEmpty) {
|
if (itemIndex == -1) {
|
||||||
|
Future.microtask(() {
|
||||||
|
ref
|
||||||
|
.read(mediaProvider.notifier)
|
||||||
|
.loadMedia(id: widget.initialItemId + 50);
|
||||||
|
});
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(),
|
appBar: AppBar(),
|
||||||
body: const Center(child: CircularProgressIndicator()),
|
body: const Center(child: CircularProgressIndicator()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_pageController == null) {
|
||||||
|
_pageController = PageController(initialPage: itemIndex);
|
||||||
|
_currentIndex = itemIndex;
|
||||||
|
_pageController!.addListener(() {
|
||||||
|
setState(() => _currentIndex = _pageController!.page?.round() ?? 0);
|
||||||
|
});
|
||||||
|
_preloadAdjacentMedia(itemIndex);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: Text('f0ck #${mediaState.mediaItems[_currentIndex].id}'),
|
title: Text('f0ck #${mediaState.mediaItems[_currentIndex].id}'),
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
context.canPop() ? context.pop() : context.go('/', extra: true);
|
||||||
|
},
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.download),
|
icon: const Icon(Icons.fullscreen),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// wip
|
_showError('download ist wip');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.download),
|
||||||
|
onPressed: _downloadMedia,
|
||||||
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
onSelected: (value) async {
|
onSelected: (value) async {
|
||||||
final item = mediaState.mediaItems[_currentIndex];
|
final item = mediaState.mediaItems[_currentIndex];
|
||||||
@ -167,7 +231,7 @@ class _DetailViewState extends ConsumerState<DetailView> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
PageTransformer(
|
PageTransformer(
|
||||||
controller: _pageController,
|
controller: _pageController!,
|
||||||
pages: mediaState.mediaItems.map((item) {
|
pages: mediaState.mediaItems.map((item) {
|
||||||
int itemIndex = mediaState.mediaItems.indexOf(item);
|
int itemIndex = mediaState.mediaItems.indexOf(item);
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
@ -186,8 +250,8 @@ class _DetailViewState extends ConsumerState<DetailView> {
|
|||||||
child: InputChip(
|
child: InputChip(
|
||||||
label: Text(mediaState.tag!),
|
label: Text(mediaState.tag!),
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
mediaNotifier.setTag(null);
|
ref.read(mediaProvider.notifier).setTag(null);
|
||||||
Navigator.pop(context);
|
context.go('/', extra: true);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -221,7 +285,7 @@ class _DetailViewState extends ConsumerState<DetailView> {
|
|||||||
if (tag.tag == 'sfw' || tag.tag == 'nsfw') return;
|
if (tag.tag == 'sfw' || tag.tag == 'nsfw') return;
|
||||||
setState(() {
|
setState(() {
|
||||||
mediaNotifier.setTag(tag.tag);
|
mediaNotifier.setTag(tag.tag);
|
||||||
Navigator.pop(context, true);
|
context.go('/', extra: true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
label: Text(tag.tag),
|
label: Text(tag.tag),
|
@ -2,11 +2,11 @@ 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:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/screens/DetailView.dart';
|
import 'package:f0ckapp/providers/media_provider.dart';
|
||||||
import 'package:f0ckapp/providers/MediaProvider.dart';
|
import 'package:f0ckapp/utils/appversion_util.dart';
|
||||||
import 'package:f0ckapp/utils/AppVersion.dart';
|
import 'package:f0ckapp/providers/theme_provider.dart';
|
||||||
import 'package:f0ckapp/providers/ThemeProvider.dart';
|
|
||||||
|
|
||||||
const List<String> mediaTypes = ["alles", "image", "video", "audio"];
|
const List<String> mediaTypes = ["alles", "image", "video", "audio"];
|
||||||
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
|
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
|
||||||
@ -208,17 +208,19 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: themes.map((themeData) {
|
children: themeMap.entries.map((entry) {
|
||||||
|
final themeName = entry.key;
|
||||||
|
final themeData = entry.value;
|
||||||
final currentTheme = ref.watch(themeNotifierProvider);
|
final currentTheme = ref.watch(themeNotifierProvider);
|
||||||
final isSelected = currentTheme == themeData.theme;
|
final isSelected = currentTheme == themeData;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(themeData.name),
|
title: Text(themeName),
|
||||||
selected: isSelected,
|
selected: isSelected,
|
||||||
selectedTileColor: Colors.blue.withValues(alpha: 0.2),
|
selectedTileColor: Colors.blue.withValues(alpha: 0.2),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await ref
|
await ref
|
||||||
.read(themeNotifierProvider.notifier)
|
.read(themeNotifierProvider.notifier)
|
||||||
.updateTheme(themeData.name);
|
.updateTheme(themeName);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
@ -226,7 +228,6 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('v${AppVersion.version}'),
|
title: Text('v${AppVersion.version}'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -279,15 +280,7 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
|
|||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
bool? ret = await Navigator.push(
|
context.push('/${item.id}', extra: true);
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => DetailView(initialItemId: item.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (ret != null && ret) {
|
|
||||||
_scrollController.jumpTo(0);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
@ -3,7 +3,7 @@ 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:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/models/MediaItem.dart';
|
import 'package:f0ckapp/models/mediaitem_model.dart';
|
||||||
|
|
||||||
final storage = FlutterSecureStorage();
|
final storage = FlutterSecureStorage();
|
||||||
|
|
@ -6,9 +6,9 @@ import 'package:cached_video_player_plus/cached_video_player_plus.dart';
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:f0ckapp/models/MediaItem.dart';
|
import 'package:f0ckapp/models/mediaitem_model.dart';
|
||||||
import 'package:f0ckapp/widgets/VideoOverlay.dart';
|
import 'package:f0ckapp/widgets/videooverlay_widget.dart';
|
||||||
import 'package:f0ckapp/providers/MediaProvider.dart';
|
import 'package:f0ckapp/providers/media_provider.dart';
|
||||||
|
|
||||||
class VideoWidget extends ConsumerStatefulWidget {
|
class VideoWidget extends ConsumerStatefulWidget {
|
||||||
final MediaItem details;
|
final MediaItem details;
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.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';
|
||||||
|
|
||||||
import 'package:f0ckapp/providers/MediaProvider.dart';
|
import 'package:f0ckapp/providers/media_provider.dart';
|
||||||
|
|
||||||
class VideoControlsOverlay extends ConsumerWidget {
|
class VideoControlsOverlay extends ConsumerWidget {
|
||||||
final CachedVideoPlayerPlusController controller;
|
final CachedVideoPlayerPlusController controller;
|
||||||
@ -25,7 +25,7 @@ class VideoControlsOverlay extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 12,
|
right: 12,
|
||||||
top: 12,
|
bottom: 12,
|
||||||
child: _ControlButton(
|
child: _ControlButton(
|
||||||
mediaState.muted ? Icons.volume_off : Icons.volume_up,
|
mediaState.muted ? Icons.volume_off : Icons.volume_up,
|
||||||
() {
|
() {
|
||||||
@ -35,13 +35,6 @@ class VideoControlsOverlay extends ConsumerWidget {
|
|||||||
size: 16,
|
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,
|
||||||
@ -89,7 +82,6 @@ class VideoControlsOverlay extends ConsumerWidget {
|
|||||||
bottom: 12,
|
bottom: 12,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_formatDuration(controller.value.position)} / ${_formatDuration(controller.value.duration)}',
|
'${_formatDuration(controller.value.position)} / ${_formatDuration(controller.value.duration)}',
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Listener(
|
Listener(
|
||||||
@ -153,7 +145,7 @@ class _ControlButton extends StatelessWidget {
|
|||||||
color: Colors.black.withValues(alpha: 0.4),
|
color: Colors.black.withValues(alpha: 0.4),
|
||||||
),
|
),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(icon, color: Colors.white, size: size),
|
icon: Icon(icon, size: size),
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
),
|
),
|
||||||
);
|
);
|
74
pubspec.lock
@ -150,6 +150,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.1"
|
version: "3.4.1"
|
||||||
|
flutter_downloader:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_downloader
|
||||||
|
sha256: "93a9ddbd561f8a3f5483b4189453fba145a0a1014a88143c96a966296b78a118"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.0"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -240,6 +248,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
go_router:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: go_router
|
||||||
|
sha256: b453934c36e289cef06525734d1e676c1f91da9e22e2017d9dcab6ce0f999175
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.1.3"
|
||||||
html:
|
html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -304,6 +320,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -369,7 +393,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
@ -416,6 +440,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
permission_handler:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: permission_handler
|
||||||
|
sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "12.0.0+1"
|
||||||
|
permission_handler_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_android
|
||||||
|
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "13.0.1"
|
||||||
|
permission_handler_apple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_apple
|
||||||
|
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.4.7"
|
||||||
|
permission_handler_html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_html
|
||||||
|
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3+5"
|
||||||
|
permission_handler_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_platform_interface
|
||||||
|
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.3.0"
|
||||||
|
permission_handler_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_windows
|
||||||
|
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.1.5+35
|
version: 1.1.10+40
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.9.0-100.2.beta
|
sdk: ^3.9.0-100.2.beta
|
||||||
@ -41,6 +41,10 @@ dependencies:
|
|||||||
share_plus: ^11.0.0
|
share_plus: ^11.0.0
|
||||||
flutter_secure_storage: ^9.2.4
|
flutter_secure_storage: ^9.2.4
|
||||||
flutter_riverpod: ^2.6.1
|
flutter_riverpod: ^2.6.1
|
||||||
|
go_router: ^15.1.3
|
||||||
|
flutter_downloader: ^1.12.0
|
||||||
|
permission_handler: ^12.0.0+1
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|