added cargo files
This commit is contained in:
8
PinePods-0.8.2/mobile/lib/core/annotations.dart
Normal file
8
PinePods-0.8.2/mobile/lib/core/annotations.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2020 Ben Hills and the project contributors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/// Simple marker to indicate a field is transient and is not intended to be persisted
|
||||
class Transient {
|
||||
const Transient();
|
||||
}
|
||||
54
PinePods-0.8.2/mobile/lib/core/environment.dart
Normal file
54
PinePods-0.8.2/mobile/lib/core/environment.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2020 Ben Hills and the project contributors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
/// The key required when searching via PodcastIndex.org.
|
||||
const podcastIndexKey = String.fromEnvironment('PINDEX_KEY', defaultValue: '');
|
||||
|
||||
/// The secret required when searching via PodcastIndex.org.
|
||||
const podcastIndexSecret = String.fromEnvironment(
|
||||
'PINDEX_SECRET',
|
||||
defaultValue: '',
|
||||
);
|
||||
|
||||
/// Allows a user to override the default user agent string.
|
||||
const userAgentAppString = String.fromEnvironment(
|
||||
'USER_AGENT',
|
||||
defaultValue: '',
|
||||
);
|
||||
|
||||
/// Link to a feedback form. This will be shown in the main overflow menu if set
|
||||
const feedbackUrl = String.fromEnvironment('FEEDBACK_URL', defaultValue: '');
|
||||
|
||||
/// This class stores version information for PinePods, including project version and
|
||||
/// build number. This is then used for user agent strings when interacting with
|
||||
/// APIs and RSS feeds.
|
||||
///
|
||||
/// The user agent string can be overridden by passing in the USER_AGENT variable
|
||||
/// using dart-define.
|
||||
class Environment {
|
||||
static const _applicationName = 'Pinepods';
|
||||
static const _applicationUrl =
|
||||
'https://github.com/madeofpendletonwool/pinepods';
|
||||
static const _projectVersion = '0.8.1';
|
||||
static const _build = '20252203';
|
||||
|
||||
static var _agentString = userAgentAppString;
|
||||
|
||||
static String userAgent() {
|
||||
if (_agentString.isEmpty) {
|
||||
var platform =
|
||||
'${Platform.operatingSystem} ${Platform.operatingSystemVersion}'
|
||||
.trim();
|
||||
|
||||
_agentString =
|
||||
'$_applicationName/$_projectVersion b$_build (phone;$platform) $_applicationUrl';
|
||||
}
|
||||
|
||||
return _agentString;
|
||||
}
|
||||
|
||||
static String get projectVersion => '$_projectVersion b$_build';
|
||||
}
|
||||
60
PinePods-0.8.2/mobile/lib/core/extensions.dart
Normal file
60
PinePods-0.8.2/mobile/lib/core/extensions.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2020 Ben Hills and the project contributors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
extension IterableExtensions<E> on Iterable<E> {
|
||||
Iterable<List<E>> chunk(int size) sync* {
|
||||
if (length <= 0) {
|
||||
yield [];
|
||||
return;
|
||||
}
|
||||
|
||||
var skip = 0;
|
||||
|
||||
while (skip < length) {
|
||||
final chunk = this.skip(skip).take(size);
|
||||
|
||||
yield chunk.toList(growable: false);
|
||||
|
||||
skip += size;
|
||||
|
||||
if (chunk.length < size) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtString on String? {
|
||||
String get forceHttps {
|
||||
if (this != null) {
|
||||
final url = Uri.tryParse(this!);
|
||||
|
||||
if (url == null || !url.isScheme('http')) return this!;
|
||||
|
||||
// Don't force HTTPS for localhost or local IP addresses to support self-hosted development
|
||||
final host = url.host.toLowerCase();
|
||||
if (host == 'localhost' ||
|
||||
host == '127.0.0.1' ||
|
||||
host.startsWith('10.') ||
|
||||
host.startsWith('192.168.') ||
|
||||
host.startsWith('172.') ||
|
||||
host.endsWith('.local')) {
|
||||
return this!;
|
||||
}
|
||||
|
||||
return url.replace(scheme: 'https').toString();
|
||||
}
|
||||
|
||||
return this ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtDouble on double {
|
||||
double get toTenth {
|
||||
var mod = pow(10.0, 1).toDouble();
|
||||
return ((this * mod).round().toDouble() / mod);
|
||||
}
|
||||
}
|
||||
140
PinePods-0.8.2/mobile/lib/core/utils.dart
Normal file
140
PinePods-0.8.2/mobile/lib/core/utils.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2020 Ben Hills and the project contributors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:pinepods_mobile/entities/episode.dart';
|
||||
import 'package:pinepods_mobile/services/settings/mobile_settings_service.dart';
|
||||
import 'package:pinepods_mobile/services/settings/settings_service.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
/// Returns the storage directory for the current platform.
|
||||
///
|
||||
/// On iOS, the directory that the app has available to it for storing episodes may
|
||||
/// change between updates, whereas on Android we are able to save the full path. To
|
||||
/// ensure we can handle the directory name change on iOS without breaking existing
|
||||
/// Android installations we have created the following three functions to help with
|
||||
/// resolving the various paths correctly depending upon platform.
|
||||
Future<String> resolvePath(Episode episode) async {
|
||||
if (Platform.isIOS) {
|
||||
return Future.value(join(await getStorageDirectory(), episode.filepath, episode.filename));
|
||||
}
|
||||
|
||||
return Future.value(join(episode.filepath!, episode.filename));
|
||||
}
|
||||
|
||||
Future<String> resolveDirectory({required Episode episode, bool full = false}) async {
|
||||
if (full || Platform.isAndroid) {
|
||||
return Future.value(join(await getStorageDirectory(), safePath(episode.podcast!)));
|
||||
}
|
||||
|
||||
return Future.value(safePath(episode.podcast!));
|
||||
}
|
||||
|
||||
Future<void> createDownloadDirectory(Episode episode) async {
|
||||
var path = join(await getStorageDirectory(), safePath(episode.podcast!));
|
||||
|
||||
Directory(path).createSync(recursive: true);
|
||||
}
|
||||
|
||||
Future<bool> hasStoragePermission() async {
|
||||
SettingsService? settings = await MobileSettingsService.instance();
|
||||
|
||||
if (Platform.isIOS || !settings!.storeDownloadsSDCard) {
|
||||
return Future.value(true);
|
||||
} else {
|
||||
final permissionStatus = await Permission.storage.request();
|
||||
|
||||
return Future.value(permissionStatus.isGranted);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getStorageDirectory() async {
|
||||
SettingsService? settings = await MobileSettingsService.instance();
|
||||
Directory directory;
|
||||
|
||||
if (Platform.isIOS) {
|
||||
directory = await getApplicationDocumentsDirectory();
|
||||
} else if (settings!.storeDownloadsSDCard) {
|
||||
directory = await _getSDCard();
|
||||
} else {
|
||||
directory = await getApplicationSupportDirectory();
|
||||
}
|
||||
|
||||
return join(directory.path, 'PinePods');
|
||||
}
|
||||
|
||||
Future<bool> hasExternalStorage() async {
|
||||
try {
|
||||
await _getSDCard();
|
||||
|
||||
return Future.value(true);
|
||||
} catch (e) {
|
||||
return Future.value(false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Directory> _getSDCard() async {
|
||||
final appDocumentDir = (await getExternalStorageDirectories(type: StorageDirectory.podcasts))!;
|
||||
|
||||
Directory? path;
|
||||
|
||||
// If the directory contains the word 'emulated' we are
|
||||
// probably looking at a mapped user partition rather than
|
||||
// an actual SD card - so skip those and find the first
|
||||
// non-emulated directory.
|
||||
if (appDocumentDir.isNotEmpty) {
|
||||
// See if we can find the last card without emulated
|
||||
for (var d in appDocumentDir) {
|
||||
if (!d.path.contains('emulated')) {
|
||||
path = d.absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (path == null) {
|
||||
throw ('No SD card found');
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/// Strips characters that are invalid for file and directory names.
|
||||
String? safePath(String? s) {
|
||||
return s?.replaceAll(RegExp(r'[^\w\s]+'), '').trim();
|
||||
}
|
||||
|
||||
String? safeFile(String? s) {
|
||||
return s?.replaceAll(RegExp(r'[^\w\s\.]+'), '').trim();
|
||||
}
|
||||
|
||||
Future<String> resolveUrl(String url, {bool forceHttps = false}) async {
|
||||
final client = HttpClient();
|
||||
var uri = Uri.parse(url);
|
||||
var request = await client.getUrl(uri);
|
||||
|
||||
request.followRedirects = false;
|
||||
|
||||
var response = await request.close();
|
||||
|
||||
while (response.isRedirect) {
|
||||
response.drain(0);
|
||||
final location = response.headers.value(HttpHeaders.locationHeader);
|
||||
if (location != null) {
|
||||
uri = uri.resolve(location);
|
||||
request = await client.getUrl(uri);
|
||||
// Set the body or headers as desired.
|
||||
request.followRedirects = false;
|
||||
response = await request.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (uri.scheme == 'http') {
|
||||
uri = uri.replace(scheme: 'https');
|
||||
}
|
||||
|
||||
return uri.toString();
|
||||
}
|
||||
Reference in New Issue
Block a user