Added source template for testing
This commit is contained in:
parent
757587a64d
commit
3dd8c8f14b
11 changed files with 220 additions and 84 deletions
|
|
@ -51,5 +51,11 @@
|
|||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -15,24 +15,26 @@ class $MSource implements MSource, $Instance {
|
|||
BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: []))
|
||||
},
|
||||
fields: {
|
||||
'id': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int))),
|
||||
'id':
|
||||
BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int))),
|
||||
'name': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string))),
|
||||
'baseUrl': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string))),
|
||||
'lang': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string))),
|
||||
'isFullData': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool))),
|
||||
'hasCloudflare': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool))),
|
||||
'isFullData':
|
||||
BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool))),
|
||||
'hasCloudflare':
|
||||
BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.bool))),
|
||||
'dateFormat': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string))),
|
||||
'dateFormatLocale': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string))),
|
||||
'apiUrl': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string))),
|
||||
'additionalParams': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string))),
|
||||
},
|
||||
wrap: true);
|
||||
|
||||
|
|
@ -69,6 +71,8 @@ class $MSource implements MSource, $Instance {
|
|||
return $String($value.dateFormatLocale!);
|
||||
case 'apiUrl':
|
||||
return $String($value.apiUrl!);
|
||||
case 'additionalParams':
|
||||
return $String($value.additionalParams!);
|
||||
|
||||
default:
|
||||
return _superclass.$getProperty(runtime, identifier);
|
||||
|
|
@ -99,6 +103,8 @@ class $MSource implements MSource, $Instance {
|
|||
$value.dateFormatLocale = value.$reified;
|
||||
case 'apiUrl':
|
||||
$value.apiUrl = value.$reified;
|
||||
case 'additionalParams':
|
||||
$value.additionalParams = value.$reified;
|
||||
default:
|
||||
_superclass.$setProperty(runtime, identifier, value);
|
||||
}
|
||||
|
|
@ -131,6 +137,9 @@ class $MSource implements MSource, $Instance {
|
|||
@override
|
||||
String? get name => $value.name;
|
||||
|
||||
@override
|
||||
String? get additionalParams => $value.additionalParams;
|
||||
|
||||
@override
|
||||
set apiUrl(String? apiUrl) {}
|
||||
|
||||
|
|
@ -157,4 +166,7 @@ class $MSource implements MSource, $Instance {
|
|||
|
||||
@override
|
||||
set name(String? name) {}
|
||||
|
||||
@override
|
||||
set additionalParams(String? additionalParams) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ class MSource {
|
|||
|
||||
String? apiUrl;
|
||||
|
||||
String? additionalParams;
|
||||
|
||||
MSource(
|
||||
{this.id,
|
||||
this.name,
|
||||
|
|
@ -26,5 +28,6 @@ class MSource {
|
|||
this.hasCloudflare,
|
||||
this.dateFormat,
|
||||
this.dateFormatLocale,
|
||||
this.apiUrl});
|
||||
this.apiUrl,
|
||||
this.additionalParams});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ class Source {
|
|||
|
||||
String? appMinVerReq;
|
||||
|
||||
String? additionalParams;
|
||||
|
||||
Source(
|
||||
{this.id = 0,
|
||||
this.name = '',
|
||||
|
|
@ -74,7 +76,8 @@ class Source {
|
|||
this.sourceCode = '',
|
||||
this.headers = '',
|
||||
this.isManga = true,
|
||||
this.appMinVerReq = ""});
|
||||
this.appMinVerReq = "",
|
||||
this.additionalParams = ""});
|
||||
|
||||
Source.fromJson(Map<String, dynamic> json) {
|
||||
apiUrl = json['apiUrl'];
|
||||
|
|
@ -100,6 +103,7 @@ class Source {
|
|||
typeSource = json['typeSource'];
|
||||
version = json['version'];
|
||||
versionLast = json['versionLast'];
|
||||
additionalParams = json['additionalParams'] ?? "";
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -127,6 +131,7 @@ class Source {
|
|||
data['typeSource'] = typeSource;
|
||||
data['version'] = version;
|
||||
data['versionLast'] = versionLast;
|
||||
data['additionalParams'] = additionalParams;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +145,7 @@ class Source {
|
|||
baseUrl: baseUrl,
|
||||
apiUrl: apiUrl,
|
||||
dateFormat: dateFormat,
|
||||
dateFormatLocale: dateFormatLocale);
|
||||
dateFormatLocale: dateFormatLocale,
|
||||
additionalParams: additionalParams);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:mangayomi/main.dart';
|
|||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/modules/browse/sources/widgets/source_list_tile.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/sources/source_test.dart';
|
||||
import 'package:mangayomi/utils/language.dart';
|
||||
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
|
||||
|
||||
|
|
@ -53,6 +54,8 @@ class SourcesScreen extends ConsumerWidget {
|
|||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
if (useTestSourceCode)
|
||||
SourceListTile(source: testSourceModel, isManga: isManga),
|
||||
GroupedListView<Source, String>(
|
||||
elements: entries,
|
||||
groupBy: (element) => "",
|
||||
|
|
|
|||
|
|
@ -76,9 +76,8 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
source: widget.source,
|
||||
page: _page + 1,
|
||||
).future);
|
||||
} else if (_selectedIndex == 2 &&
|
||||
_isSearch &&
|
||||
(_query.isNotEmpty || _isFiltering)) {
|
||||
} else if (_selectedIndex == 2 && (_isSearch && _query.isNotEmpty) ||
|
||||
_isFiltering) {
|
||||
mangaResList = await ref.watch(searchProvider(
|
||||
source: widget.source,
|
||||
query: _query,
|
||||
|
|
@ -105,19 +104,15 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filterList = getFilterList(source: widget.source);
|
||||
if (_selectedIndex == 2 &&
|
||||
_isSearch &&
|
||||
(_query.isNotEmpty || _isFiltering)) {
|
||||
if (_selectedIndex == 2 && (_isSearch && _query.isNotEmpty) ||
|
||||
_isFiltering) {
|
||||
_getManga = ref.watch(searchProvider(
|
||||
source: widget.source, query: _query, page: 1, filterList: filters));
|
||||
} else if (_selectedIndex == 1 && !_isSearch && _query.isEmpty) {
|
||||
_getManga =
|
||||
ref.watch(getLatestUpdatesProvider(source: widget.source, page: 1));
|
||||
} else if (_selectedIndex == 0 && !_isSearch && _query.isEmpty) {
|
||||
_getManga = ref.watch(getPopularProvider(
|
||||
source: widget.source,
|
||||
page: 1,
|
||||
));
|
||||
_getManga = ref.watch(getPopularProvider(source: widget.source, page: 1));
|
||||
}
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return Scaffold(
|
||||
|
|
@ -198,6 +193,9 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
shrinkWrap: true,
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) {
|
||||
if (filterList.isEmpty && index == 2) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return MangasCardSelector(
|
||||
icon: _types(context)[index].icon,
|
||||
selected: _selectedIndex == index,
|
||||
|
|
@ -206,7 +204,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
if (filters.isEmpty) {
|
||||
filters = filterList;
|
||||
}
|
||||
if (filters.isNotEmpty && index == 2) {
|
||||
if (index == 2) {
|
||||
final result = await showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
|
|
@ -228,10 +226,18 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
),
|
||||
const Spacer(),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
primaryColor(context)),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, 'filter');
|
||||
},
|
||||
child: Text(l10n.filter),
|
||||
child: Text(
|
||||
l10n.filter,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.scaffoldBackgroundColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -256,7 +262,6 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
setState(() {
|
||||
_selectedIndex = 2;
|
||||
_isFiltering = true;
|
||||
_isSearch = true;
|
||||
_page = 1;
|
||||
});
|
||||
}
|
||||
|
|
@ -271,6 +276,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
setState(() {
|
||||
_selectedIndex = index;
|
||||
_isFiltering = false;
|
||||
_isSearch = false;
|
||||
_page = 1;
|
||||
});
|
||||
}
|
||||
|
|
@ -416,8 +422,8 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
IconButton(
|
||||
onPressed: () {
|
||||
if (_selectedIndex == 2 &&
|
||||
_isSearch &&
|
||||
(_query.isNotEmpty || _isFiltering)) {
|
||||
(_isSearch && _query.isNotEmpty) ||
|
||||
_isFiltering) {
|
||||
ref.invalidate(searchProvider(
|
||||
source: widget.source,
|
||||
query: _query,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/eval/model/filter.dart';
|
||||
import 'package:mangayomi/utils/colors.dart';
|
||||
import 'package:mangayomi/utils/media_query.dart';
|
||||
|
||||
class FilterWidget extends StatelessWidget {
|
||||
final List<dynamic> filterList;
|
||||
|
|
@ -19,26 +20,13 @@ class FilterWidget extends StatelessWidget {
|
|||
final filterState = filterList[idx];
|
||||
Widget? widget;
|
||||
if (filterState is TextFilter) {
|
||||
widget = Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextField(
|
||||
widget = SeachFormTextFieldWidget(
|
||||
text: filterState.state,
|
||||
onChanged: (val) {
|
||||
filterList[idx] = filterState..state = val;
|
||||
onChanged(filterList);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: secondaryColor(context)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor(context)),
|
||||
),
|
||||
border: const OutlineInputBorder(borderSide: BorderSide()),
|
||||
labelText: filterState.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
labelText: filterState.name);
|
||||
} else if (filterState is HeaderFilter) {
|
||||
widget = ListTile(dense: true, title: Text(filterState.name));
|
||||
} else if (filterState is SeparatorFilter) {
|
||||
|
|
@ -117,43 +105,46 @@ class FilterWidget extends StatelessWidget {
|
|||
}).toList(),
|
||||
);
|
||||
} else if (filterState is SelectFilter) {
|
||||
widget = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
title: Text(filterState.name),
|
||||
widget = SizedBox(
|
||||
width: mediaWidth(context, 1),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
title: Text(filterState.name),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 25),
|
||||
child: DropdownButton(
|
||||
icon: const Icon(Icons.keyboard_arrow_down),
|
||||
isExpanded: true,
|
||||
value: filterState.values[filterState.state],
|
||||
hint: Text(filterState.name,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
items: filterState.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(e.name,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
filterState.state = filterState.values
|
||||
.indexWhere((element) => element == value);
|
||||
onChanged(filterList);
|
||||
},
|
||||
Expanded(
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 25),
|
||||
child: DropdownButton(
|
||||
icon: const Icon(Icons.keyboard_arrow_down),
|
||||
isExpanded: true,
|
||||
value: filterState.values[filterState.state],
|
||||
hint: Text(filterState.name,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
items: filterState.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(e.name,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
filterState.state = filterState.values
|
||||
.indexWhere((element) => element == value);
|
||||
onChanged(filterList);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return widget ?? const SizedBox.shrink();
|
||||
|
|
@ -161,3 +152,43 @@ class FilterWidget extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SeachFormTextFieldWidget extends StatefulWidget {
|
||||
final String labelText;
|
||||
final String text;
|
||||
final Function(String) onChanged;
|
||||
const SeachFormTextFieldWidget(
|
||||
{super.key,
|
||||
required this.text,
|
||||
required this.onChanged,
|
||||
required this.labelText});
|
||||
|
||||
@override
|
||||
State<SeachFormTextFieldWidget> createState() =>
|
||||
_SeachFormTextFieldWidgetState();
|
||||
}
|
||||
|
||||
class _SeachFormTextFieldWidgetState extends State<SeachFormTextFieldWidget> {
|
||||
late final _controller = TextEditingController(text: widget.text);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
onChanged: widget.onChanged,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
filled: false,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: secondaryColor(context)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor(context)),
|
||||
),
|
||||
border: const OutlineInputBorder(borderSide: BorderSide()),
|
||||
labelText: widget.labelText,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class BackupAndRestore extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isIOS = Platform.isIOS;
|
||||
// final isIOS = Platform.isIOS;
|
||||
final backupFrequency = ref.watch(backupFrequencyStateProvider);
|
||||
final backupFrequencyOptions =
|
||||
ref.watch(backupFrequencyOptionsStateProvider);
|
||||
|
|
@ -81,12 +81,12 @@ class BackupAndRestore extends ConsumerWidget {
|
|||
)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (isIOS) {
|
||||
ref.watch(doBackUpProvider(
|
||||
list: indexList,
|
||||
path: autoBackupLocation.$1,
|
||||
context: context));
|
||||
} else {
|
||||
// if (isIOS) {
|
||||
// ref.watch(doBackUpProvider(
|
||||
// list: indexList,
|
||||
// path: autoBackupLocation.$1,
|
||||
// context: context));
|
||||
// } else {
|
||||
final result = await FilePicker.platform
|
||||
.getDirectoryPath();
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ class BackupAndRestore extends ConsumerWidget {
|
|||
path: result,
|
||||
context: context));
|
||||
}
|
||||
}
|
||||
// }
|
||||
},
|
||||
child: Text(
|
||||
l10n.ok,
|
||||
|
|
@ -258,7 +258,7 @@ class BackupAndRestore extends ConsumerWidget {
|
|||
style: TextStyle(fontSize: 11, color: secondaryColor(context)),
|
||||
),
|
||||
),
|
||||
if (!isIOS)
|
||||
// if (!isIOS)
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
String? result = await FilePicker.platform.getDirectoryPath();
|
||||
|
|
@ -354,7 +354,7 @@ class BackupAndRestore extends ConsumerWidget {
|
|||
style: TextStyle(fontSize: 11, color: secondaryColor(context)),
|
||||
),
|
||||
),
|
||||
if (isIOS)
|
||||
// if (isIOS)
|
||||
ListBackupFilesFromDirectory(directory: autoBackupLocation.$1),
|
||||
ListTile(
|
||||
title: Padding(
|
||||
|
|
|
|||
|
|
@ -1,2 +1,65 @@
|
|||
import 'package:mangayomi/models/source.dart';
|
||||
|
||||
//For testing purposes, set to true
|
||||
const useTestSourceCode = false;
|
||||
const testSourceCode = r'''''';
|
||||
|
||||
final testSourceModel = Source(
|
||||
name: "Test Source",
|
||||
// Example: https://gogoanime3.net
|
||||
baseUrl: "",
|
||||
// Example: en
|
||||
lang: "",
|
||||
// Example: false for anime or true for manga
|
||||
isManga: false);
|
||||
|
||||
const testSourceCode = r'''
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class TestSource extends MProvider {
|
||||
TestSource();
|
||||
|
||||
@override
|
||||
Future<MPages> getPopular(MSource source, int page) async {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> getLatestUpdates(MSource source, int page) async {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> search(
|
||||
MSource source, String query, int page, FilterList filterList) async {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MManga> getDetail(MSource source, String url) async {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
// For anime videos
|
||||
@override
|
||||
Future<List<MVideo>> getVideoList(MSource source, String url) async {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
// For manga pages
|
||||
@override
|
||||
Future<List<String>> getPageList(MSource source, String url) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getFilterList() {
|
||||
// TODO: implement
|
||||
}
|
||||
}
|
||||
|
||||
TestSource main() {
|
||||
return TestSource();
|
||||
}
|
||||
|
||||
''';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/sources/source_test.dart';
|
||||
|
||||
Source? getSource(String lang, String name) {
|
||||
if (useTestSourceCode) {
|
||||
return testSourceModel;
|
||||
}
|
||||
try {
|
||||
final sourcesList = isar.sources.filter().idIsNotNull().findAllSync();
|
||||
return sourcesList.firstWhere(
|
||||
|
|
|
|||
|
|
@ -10,5 +10,7 @@
|
|||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
Loading…
Reference in a new issue