refactor:log, analytics, main
parent
d01f8e84b4
commit
37cd9272e7
|
|
@ -94,7 +94,7 @@ EXTERNAL SOURCES:
|
|||
|
||||
SPEC CHECKSUMS:
|
||||
camera_avfoundation: 3125e8cd1a4387f6f31c6c63abb8a55892a9eeeb
|
||||
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
abstract class Routes {
|
||||
static const INITIAL = '/';
|
||||
static const LOGIN = '/login';
|
||||
static const CAMERA = '/camera';
|
||||
static const LIST = '/list';
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:universal_io/io.dart'; // instead of 'dart:io';
|
||||
import 'dart:convert';
|
||||
|
||||
/// Plausible class. Use the constructor to set the parameters.
|
||||
class PlausibleTracker {
|
||||
/// The url of your plausible server e.g. https://plausible.io
|
||||
String serverUrl;
|
||||
String userAgent;
|
||||
String domain;
|
||||
String screenWidth;
|
||||
String xForwardedFor;
|
||||
bool enabled = true;
|
||||
|
||||
/// Constructor
|
||||
PlausibleTracker(this.serverUrl, this.domain,
|
||||
{this.userAgent = "",
|
||||
this.screenWidth = "",
|
||||
this.xForwardedFor = "127.0.0.1"});
|
||||
|
||||
/// Post event to plausible
|
||||
Future<int> event(
|
||||
{String name = "pageview",
|
||||
String referrer = "",
|
||||
String page = "",
|
||||
Map<String, String> props = const {}}) async {
|
||||
if (!enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Post-edit parameters
|
||||
int lastCharIndex = serverUrl.length - 1;
|
||||
if (serverUrl.toString()[lastCharIndex] == '/') {
|
||||
// Remove trailing slash '/'
|
||||
serverUrl = serverUrl.substring(0, lastCharIndex);
|
||||
}
|
||||
page = "app://localhost/" + page;
|
||||
referrer = "app://localhost/" + referrer;
|
||||
|
||||
// Get and set device infos
|
||||
String version = Platform.operatingSystemVersion.replaceAll('"', '');
|
||||
|
||||
if (userAgent == "") {
|
||||
userAgent = "Mozilla/5.0 ($version; rv:53.0) Gecko/20100101 Chrome/53.0";
|
||||
}
|
||||
|
||||
// Http Post request see https://plausible.io/docs/events-api
|
||||
try {
|
||||
HttpClient client = HttpClient();
|
||||
HttpClientRequest request =
|
||||
await client.postUrl(Uri.parse(serverUrl + '/api/event'));
|
||||
request.headers.set('User-Agent', userAgent);
|
||||
request.headers.set('Content-Type', 'application/json; charset=utf-8');
|
||||
request.headers.set('X-Forwarded-For', xForwardedFor);
|
||||
Object body = {
|
||||
"domain": domain,
|
||||
"name": name,
|
||||
"url": page,
|
||||
"referrer": referrer,
|
||||
"screen_width": screenWidth,
|
||||
"props": props,
|
||||
};
|
||||
request.write(json.encode(body));
|
||||
final HttpClientResponse response = await request.close();
|
||||
client.close();
|
||||
return response.statusCode;
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// check if plausible is UP
|
||||
Future<bool> hello() async {
|
||||
try {
|
||||
final client = HttpClient();
|
||||
final request = await client.getUrl(Uri.parse(serverUrl + '/api/health'));
|
||||
final response = await request.close();
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseBody = await response.transform(utf8.decoder).join();
|
||||
final json = jsonDecode(responseBody);
|
||||
|
||||
final clickhouseStatus = json['clickhouse'];
|
||||
final postgresStatus = json['postgres'];
|
||||
final sitesCacheStatus = json['sites_cache'];
|
||||
|
||||
return clickhouseStatus == 'ok' &&
|
||||
postgresStatus == 'ok' &&
|
||||
sitesCacheStatus == 'ok';
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import "package:mobdr/main.dart";
|
||||
|
||||
class dbLog {
|
||||
static void addLog(
|
||||
String type, String module, String libelle, int duree) async {
|
||||
objectbox.addLog(type, module, libelle, duree);
|
||||
}
|
||||
}
|
||||
289
lib/main.dart
289
lib/main.dart
|
|
@ -1,36 +1,32 @@
|
|||
// ignore_for_file: prefer_const_constructors
|
||||
|
||||
import 'dart:ui';
|
||||
//import 'dart:developer' as developer;
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'objectbox.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
|
||||
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||
|
||||
import 'package:mobdr/service/logger_util.dart';
|
||||
import 'package:mobdr/config/constant.dart';
|
||||
|
||||
import 'package:mobdr/core/routes/plausible_tracker.dart';
|
||||
import 'package:mobdr/cubit/language/language_cubit.dart';
|
||||
import 'package:mobdr/cubit/language/app_localizations.dart';
|
||||
import 'package:mobdr/cubit/language/initial_language.dart';
|
||||
import 'package:mobdr/service/shared_prefs.dart';
|
||||
import 'package:mobdr/ui/splash_screen.dart';
|
||||
import 'package:mobdr/service/device_info.dart';
|
||||
import 'package:mobdr/service/directories.dart';
|
||||
import 'package:mobdr/service/plausible.dart';
|
||||
import 'package:mobdr/db/db_log.dart';
|
||||
|
||||
/// Provides access to the ObjectBox Store throughout the app.
|
||||
late ObjectBox objectbox;
|
||||
|
||||
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
late PlausibleTracker plausible;
|
||||
|
||||
final EventBus eventBus = EventBus();
|
||||
|
||||
|
|
@ -39,12 +35,39 @@ Future<void> main() async {
|
|||
// to store the database in.
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await SharedPrefs().init();
|
||||
// registration of the observer
|
||||
WidgetsBinding.instance.addObserver(MyApp());
|
||||
|
||||
LoggerUtil.logNStackInfo('Application MobDR started');
|
||||
|
||||
// create database object
|
||||
objectbox = await ObjectBox.create();
|
||||
|
||||
/// Log
|
||||
objectbox.addLog('LOG', 'MOBDR', 'Ouverture application ', 0);
|
||||
// initialize shared preferences
|
||||
await SharedPrefs().init();
|
||||
|
||||
// get/set device informations
|
||||
await device_info_plus().initPlatformState();
|
||||
|
||||
// initialize directories
|
||||
await directories().initDirectories();
|
||||
|
||||
/// tracker analytics
|
||||
await PlausibleUtil.initializePlausible(window.physicalSize.width);
|
||||
|
||||
// url MP4
|
||||
SharedPrefs().urlMP4 =
|
||||
'https://mp4.ikksgroup.com/MobilePortal4/index.html#ajax/dashboard.html';
|
||||
|
||||
PlausibleUtil.addEvent(
|
||||
name: 'access',
|
||||
page: 'access',
|
||||
referrer: 'referrerPage',
|
||||
props: {
|
||||
'name': SharedPrefs().login,
|
||||
});
|
||||
|
||||
dbLog.addLog('LOG', 'MOBDR', 'Ouverture application ', 0);
|
||||
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
|
||||
.then((_) {
|
||||
|
|
@ -53,7 +76,7 @@ Future<void> main() async {
|
|||
Wakelock.enable();
|
||||
|
||||
eventBus.on().listen((event) {
|
||||
print('${DateTime.now()} Event: $event');
|
||||
LoggerUtil.logVerbose('${DateTime.now()} Event: $event');
|
||||
});
|
||||
|
||||
runApp(MyApp());
|
||||
|
|
@ -70,17 +93,30 @@ class MyCustomScrollBehavior extends MaterialScrollBehavior {
|
|||
};
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
class MyApp extends StatelessWidget with WidgetsBindingObserver {
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) async {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
LoggerUtil.logNStackInfo("The application is in foreground");
|
||||
|
||||
// check if plausible is UP
|
||||
plausible.enabled = await plausible.hello();
|
||||
|
||||
plausible.event(
|
||||
name: 'access',
|
||||
page: 'access',
|
||||
referrer: 'referrerPage',
|
||||
props: {
|
||||
'name': SharedPrefs().login,
|
||||
});
|
||||
} else if (state == AppLifecycleState.inactive) {
|
||||
LoggerUtil.logNStackInfo("The application runs in the background");
|
||||
}
|
||||
}
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
initDirectories();
|
||||
initPlatformState();
|
||||
|
||||
// url MP4
|
||||
SharedPrefs().urlMP4 =
|
||||
'https://mp4.ikksgroup.com/MobilePortal4/index.html#ajax/dashboard.html';
|
||||
|
||||
// Initialize all bloc provider used on this entire application here
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
|
|
@ -139,211 +175,4 @@ class MyApp extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
void initDirectories() async {
|
||||
// Get the application's document directory
|
||||
final Directory documentsDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
// Create a Directory object for the "photos" directory in the documents directory
|
||||
final Directory photosDir = Directory('${documentsDir.path}/photos');
|
||||
|
||||
// Check if the "photos" directory exists
|
||||
if (await photosDir.exists()) {
|
||||
print(
|
||||
'The "photos" directory already exists in the documents directory.');
|
||||
} else {
|
||||
// Create the "photos" directory if it does not exist
|
||||
await photosDir.create();
|
||||
print(
|
||||
'The "photos" directory has been created in the documents directory.');
|
||||
}
|
||||
|
||||
// save directories into shared Prefs
|
||||
SharedPrefs().documentsDir = documentsDir.path;
|
||||
SharedPrefs().photosDir = photosDir.path;
|
||||
|
||||
// Get a list of all files in the "photos" directory
|
||||
final List<FileSystemEntity> files = await photosDir.list().toList();
|
||||
|
||||
// Print out the names of the files in the directory
|
||||
for (FileSystemEntity file in files) {
|
||||
print('File name: ${file.path.split('/').last}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initPlatformState() async {
|
||||
var deviceData = <String, dynamic>{};
|
||||
|
||||
try {
|
||||
if (kIsWeb) {
|
||||
deviceData = _readWebBrowserInfo(await deviceInfoPlugin.webBrowserInfo);
|
||||
} else {
|
||||
if (Platform.isAndroid) {
|
||||
deviceData =
|
||||
_readAndroidBuildData(await deviceInfoPlugin.androidInfo);
|
||||
} else if (Platform.isIOS) {
|
||||
deviceData = _readIosDeviceInfo(await deviceInfoPlugin.iosInfo);
|
||||
} else if (Platform.isLinux) {
|
||||
deviceData = _readLinuxDeviceInfo(await deviceInfoPlugin.linuxInfo);
|
||||
} else if (Platform.isMacOS) {
|
||||
deviceData = _readMacOsDeviceInfo(await deviceInfoPlugin.macOsInfo);
|
||||
} else if (Platform.isWindows) {
|
||||
deviceData =
|
||||
_readWindowsDeviceInfo(await deviceInfoPlugin.windowsInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// save if we are on a simulator
|
||||
if (deviceData['isPhysicalDevice'] == false) {
|
||||
SharedPrefs().isSimulator = true;
|
||||
}
|
||||
|
||||
print(deviceData);
|
||||
} on PlatformException {
|
||||
deviceData = <String, dynamic>{
|
||||
'Error:': 'Failed to get platform version.'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readAndroidBuildData(AndroidDeviceInfo build) {
|
||||
return <String, dynamic>{
|
||||
'version.securityPatch': build.version.securityPatch,
|
||||
'version.sdkInt': build.version.sdkInt,
|
||||
'version.release': build.version.release,
|
||||
'version.previewSdkInt': build.version.previewSdkInt,
|
||||
'version.incremental': build.version.incremental,
|
||||
'version.codename': build.version.codename,
|
||||
'version.baseOS': build.version.baseOS,
|
||||
'board': build.board,
|
||||
'bootloader': build.bootloader,
|
||||
'brand': build.brand,
|
||||
'device': build.device,
|
||||
'display': build.display,
|
||||
'fingerprint': build.fingerprint,
|
||||
'hardware': build.hardware,
|
||||
'host': build.host,
|
||||
'id': build.id,
|
||||
'manufacturer': build.manufacturer,
|
||||
'model': build.model,
|
||||
'product': build.product,
|
||||
'supported32BitAbis': build.supported32BitAbis,
|
||||
'supported64BitAbis': build.supported64BitAbis,
|
||||
'supportedAbis': build.supportedAbis,
|
||||
'tags': build.tags,
|
||||
'type': build.type,
|
||||
'isPhysicalDevice': build.isPhysicalDevice,
|
||||
'systemFeatures': build.systemFeatures,
|
||||
'displaySizeInches':
|
||||
((build.displayMetrics.sizeInches * 10).roundToDouble() / 10),
|
||||
'displayWidthPixels': build.displayMetrics.widthPx,
|
||||
'displayWidthInches': build.displayMetrics.widthInches,
|
||||
'displayHeightPixels': build.displayMetrics.heightPx,
|
||||
'displayHeightInches': build.displayMetrics.heightInches,
|
||||
'displayXDpi': build.displayMetrics.xDpi,
|
||||
'displayYDpi': build.displayMetrics.yDpi,
|
||||
'serialNumber': build.serialNumber,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readIosDeviceInfo(IosDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'name': data.name,
|
||||
'systemName': data.systemName,
|
||||
'systemVersion': data.systemVersion,
|
||||
'model': data.model,
|
||||
'localizedModel': data.localizedModel,
|
||||
'identifierForVendor': data.identifierForVendor,
|
||||
'isPhysicalDevice': data.isPhysicalDevice,
|
||||
'utsname.sysname:': data.utsname.sysname,
|
||||
'utsname.nodename:': data.utsname.nodename,
|
||||
'utsname.release:': data.utsname.release,
|
||||
'utsname.version:': data.utsname.version,
|
||||
'utsname.machine:': data.utsname.machine,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readLinuxDeviceInfo(LinuxDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'name': data.name,
|
||||
'version': data.version,
|
||||
'id': data.id,
|
||||
'idLike': data.idLike,
|
||||
'versionCodename': data.versionCodename,
|
||||
'versionId': data.versionId,
|
||||
'prettyName': data.prettyName,
|
||||
'buildId': data.buildId,
|
||||
'variant': data.variant,
|
||||
'variantId': data.variantId,
|
||||
'machineId': data.machineId,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readWebBrowserInfo(WebBrowserInfo data) {
|
||||
return <String, dynamic>{
|
||||
'browserName': describeEnum(data.browserName),
|
||||
'appCodeName': data.appCodeName,
|
||||
'appName': data.appName,
|
||||
'appVersion': data.appVersion,
|
||||
'deviceMemory': data.deviceMemory,
|
||||
'language': data.language,
|
||||
'languages': data.languages,
|
||||
'platform': data.platform,
|
||||
'product': data.product,
|
||||
'productSub': data.productSub,
|
||||
'userAgent': data.userAgent,
|
||||
'vendor': data.vendor,
|
||||
'vendorSub': data.vendorSub,
|
||||
'hardwareConcurrency': data.hardwareConcurrency,
|
||||
'maxTouchPoints': data.maxTouchPoints,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readMacOsDeviceInfo(MacOsDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'computerName': data.computerName,
|
||||
'hostName': data.hostName,
|
||||
'arch': data.arch,
|
||||
'model': data.model,
|
||||
'kernelVersion': data.kernelVersion,
|
||||
'majorVersion': data.majorVersion,
|
||||
'minorVersion': data.minorVersion,
|
||||
'patchVersion': data.patchVersion,
|
||||
'osRelease': data.osRelease,
|
||||
'activeCPUs': data.activeCPUs,
|
||||
'memorySize': data.memorySize,
|
||||
'cpuFrequency': data.cpuFrequency,
|
||||
'systemGUID': data.systemGUID,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readWindowsDeviceInfo(WindowsDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'numberOfCores': data.numberOfCores,
|
||||
'computerName': data.computerName,
|
||||
'systemMemoryInMegabytes': data.systemMemoryInMegabytes,
|
||||
'userName': data.userName,
|
||||
'majorVersion': data.majorVersion,
|
||||
'minorVersion': data.minorVersion,
|
||||
'buildNumber': data.buildNumber,
|
||||
'platformId': data.platformId,
|
||||
'csdVersion': data.csdVersion,
|
||||
'servicePackMajor': data.servicePackMajor,
|
||||
'servicePackMinor': data.servicePackMinor,
|
||||
'suitMask': data.suitMask,
|
||||
'productType': data.productType,
|
||||
'reserved': data.reserved,
|
||||
'buildLab': data.buildLab,
|
||||
'buildLabEx': data.buildLabEx,
|
||||
'digitalProductId': data.digitalProductId,
|
||||
'displayVersion': data.displayVersion,
|
||||
'editionId': data.editionId,
|
||||
'installDate': data.installDate,
|
||||
'productId': data.productId,
|
||||
'productName': data.productName,
|
||||
'registeredOwner': data.registeredOwner,
|
||||
'releaseId': data.releaseId,
|
||||
'deviceId': data.deviceId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
|
||||
Future<String> getPublicIPAddress() async {
|
||||
try {
|
||||
var response =
|
||||
await http.get(Uri.parse('https://api.ipify.org/?format=json'));
|
||||
if (response.statusCode == 200) {
|
||||
var data = json.decode(response.body);
|
||||
return data['ip'];
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
return '127.0.0.1';
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:mobdr/service/logger_util.dart';
|
||||
import 'package:mobdr/service/shared_prefs.dart';
|
||||
|
||||
class device_info_plus {
|
||||
static final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
|
||||
Future<void> initPlatformState() async {
|
||||
var deviceData = <String, dynamic>{};
|
||||
|
||||
try {
|
||||
if (kIsWeb) {
|
||||
deviceData = _readWebBrowserInfo(await deviceInfoPlugin.webBrowserInfo);
|
||||
} else {
|
||||
if (Platform.isAndroid) {
|
||||
deviceData =
|
||||
_readAndroidBuildData(await deviceInfoPlugin.androidInfo);
|
||||
} else if (Platform.isIOS) {
|
||||
deviceData = _readIosDeviceInfo(await deviceInfoPlugin.iosInfo);
|
||||
} else if (Platform.isLinux) {
|
||||
deviceData = _readLinuxDeviceInfo(await deviceInfoPlugin.linuxInfo);
|
||||
} else if (Platform.isMacOS) {
|
||||
deviceData = _readMacOsDeviceInfo(await deviceInfoPlugin.macOsInfo);
|
||||
} else if (Platform.isWindows) {
|
||||
deviceData =
|
||||
_readWindowsDeviceInfo(await deviceInfoPlugin.windowsInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// save if we are on a simulator
|
||||
if (deviceData['isPhysicalDevice'] == false) {
|
||||
SharedPrefs().isSimulator = true;
|
||||
}
|
||||
|
||||
if (deviceData['userAgent'] == null) {
|
||||
SharedPrefs().mobileUserAgent = buildUserAgent(deviceData);
|
||||
} else {
|
||||
SharedPrefs().mobileUserAgent = deviceData['userAgent'];
|
||||
}
|
||||
|
||||
LoggerUtil.logNStackInfo('Devicedata:' + deviceData.toString());
|
||||
} on PlatformException {
|
||||
deviceData = <String, dynamic>{
|
||||
'Error:': 'Failed to get platform version.'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readAndroidBuildData(AndroidDeviceInfo build) {
|
||||
return <String, dynamic>{
|
||||
'version.securityPatch': build.version.securityPatch,
|
||||
'version.sdkInt': build.version.sdkInt,
|
||||
'version.release': build.version.release,
|
||||
'version.previewSdkInt': build.version.previewSdkInt,
|
||||
'version.incremental': build.version.incremental,
|
||||
'version.codename': build.version.codename,
|
||||
'version.baseOS': build.version.baseOS,
|
||||
'board': build.board,
|
||||
'bootloader': build.bootloader,
|
||||
'brand': build.brand,
|
||||
'device': build.device,
|
||||
'display': build.display,
|
||||
'fingerprint': build.fingerprint,
|
||||
'hardware': build.hardware,
|
||||
'host': build.host,
|
||||
'id': build.id,
|
||||
'manufacturer': build.manufacturer,
|
||||
'model': build.model,
|
||||
'product': build.product,
|
||||
'supported32BitAbis': build.supported32BitAbis,
|
||||
'supported64BitAbis': build.supported64BitAbis,
|
||||
'supportedAbis': build.supportedAbis,
|
||||
'tags': build.tags,
|
||||
'type': build.type,
|
||||
'isPhysicalDevice': build.isPhysicalDevice,
|
||||
'systemFeatures': build.systemFeatures,
|
||||
'displaySizeInches':
|
||||
((build.displayMetrics.sizeInches * 10).roundToDouble() / 10),
|
||||
'displayWidthPixels': build.displayMetrics.widthPx,
|
||||
'displayWidthInches': build.displayMetrics.widthInches,
|
||||
'displayHeightPixels': build.displayMetrics.heightPx,
|
||||
'displayHeightInches': build.displayMetrics.heightInches,
|
||||
'displayXDpi': build.displayMetrics.xDpi,
|
||||
'displayYDpi': build.displayMetrics.yDpi,
|
||||
'serialNumber': build.serialNumber,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readIosDeviceInfo(IosDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'name': data.name,
|
||||
'systemName': data.systemName,
|
||||
'systemVersion': data.systemVersion,
|
||||
'model': data.model,
|
||||
'localizedModel': data.localizedModel,
|
||||
'identifierForVendor': data.identifierForVendor,
|
||||
'isPhysicalDevice': data.isPhysicalDevice,
|
||||
'utsname.sysname:': data.utsname.sysname,
|
||||
'utsname.nodename:': data.utsname.nodename,
|
||||
'utsname.release:': data.utsname.release,
|
||||
'utsname.version:': data.utsname.version,
|
||||
'utsname.machine:': data.utsname.machine,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readLinuxDeviceInfo(LinuxDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'name': data.name,
|
||||
'version': data.version,
|
||||
'id': data.id,
|
||||
'idLike': data.idLike,
|
||||
'versionCodename': data.versionCodename,
|
||||
'versionId': data.versionId,
|
||||
'prettyName': data.prettyName,
|
||||
'buildId': data.buildId,
|
||||
'variant': data.variant,
|
||||
'variantId': data.variantId,
|
||||
'machineId': data.machineId,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readWebBrowserInfo(WebBrowserInfo data) {
|
||||
return <String, dynamic>{
|
||||
'browserName': describeEnum(data.browserName),
|
||||
'appCodeName': data.appCodeName,
|
||||
'appName': data.appName,
|
||||
'appVersion': data.appVersion,
|
||||
'deviceMemory': data.deviceMemory,
|
||||
'language': data.language,
|
||||
'languages': data.languages,
|
||||
'platform': data.platform,
|
||||
'product': data.product,
|
||||
'productSub': data.productSub,
|
||||
'userAgent': data.userAgent,
|
||||
'vendor': data.vendor,
|
||||
'vendorSub': data.vendorSub,
|
||||
'hardwareConcurrency': data.hardwareConcurrency,
|
||||
'maxTouchPoints': data.maxTouchPoints,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readMacOsDeviceInfo(MacOsDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'computerName': data.computerName,
|
||||
'hostName': data.hostName,
|
||||
'arch': data.arch,
|
||||
'model': data.model,
|
||||
'kernelVersion': data.kernelVersion,
|
||||
'majorVersion': data.majorVersion,
|
||||
'minorVersion': data.minorVersion,
|
||||
'patchVersion': data.patchVersion,
|
||||
'osRelease': data.osRelease,
|
||||
'activeCPUs': data.activeCPUs,
|
||||
'memorySize': data.memorySize,
|
||||
'cpuFrequency': data.cpuFrequency,
|
||||
'systemGUID': data.systemGUID,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readWindowsDeviceInfo(WindowsDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'numberOfCores': data.numberOfCores,
|
||||
'computerName': data.computerName,
|
||||
'systemMemoryInMegabytes': data.systemMemoryInMegabytes,
|
||||
'userName': data.userName,
|
||||
'majorVersion': data.majorVersion,
|
||||
'minorVersion': data.minorVersion,
|
||||
'buildNumber': data.buildNumber,
|
||||
'platformId': data.platformId,
|
||||
'csdVersion': data.csdVersion,
|
||||
'servicePackMajor': data.servicePackMajor,
|
||||
'servicePackMinor': data.servicePackMinor,
|
||||
'suitMask': data.suitMask,
|
||||
'productType': data.productType,
|
||||
'reserved': data.reserved,
|
||||
'buildLab': data.buildLab,
|
||||
'buildLabEx': data.buildLabEx,
|
||||
'digitalProductId': data.digitalProductId,
|
||||
'displayVersion': data.displayVersion,
|
||||
'editionId': data.editionId,
|
||||
'installDate': data.installDate,
|
||||
'productId': data.productId,
|
||||
'productName': data.productName,
|
||||
'registeredOwner': data.registeredOwner,
|
||||
'releaseId': data.releaseId,
|
||||
'deviceId': data.deviceId,
|
||||
};
|
||||
}
|
||||
|
||||
String buildUserAgent(Map<String, dynamic> deviceData) {
|
||||
String systemName = deviceData['systemName'];
|
||||
String systemVersion = deviceData['systemVersion'];
|
||||
String model = deviceData['model'];
|
||||
|
||||
return '$systemName $systemVersion; $model';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import 'dart:io';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:mobdr/service/shared_prefs.dart';
|
||||
|
||||
class directories {
|
||||
Future<void> initDirectories() async {
|
||||
// Get the application's document directory
|
||||
final Directory documentsDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
// Directory object for the "photos" directory in the documents directory
|
||||
final Directory photosDir = Directory('${documentsDir.path}/photos');
|
||||
|
||||
// Directory object for the "cache" directory in the documents directory
|
||||
final Directory cacheDir = await getTemporaryDirectory();
|
||||
|
||||
// Check if the "photos" directory exists
|
||||
if (await photosDir.exists()) {
|
||||
print(
|
||||
'The "photos" directory already exists in the documents directory.');
|
||||
} else {
|
||||
// Create the "photos" directory if it does not exist
|
||||
await photosDir.create();
|
||||
print(
|
||||
'The "photos" directory has been created in the documents directory.');
|
||||
}
|
||||
|
||||
// save directories into shared Prefs
|
||||
SharedPrefs().documentsDir = documentsDir.path;
|
||||
SharedPrefs().photosDir = photosDir.path;
|
||||
SharedPrefs().cacheDir = cacheDir.path;
|
||||
|
||||
// Get a list of all files in the "photos" directory
|
||||
List<FileSystemEntity> files = await photosDir.list().toList();
|
||||
|
||||
// Print out the names of the files in the directory
|
||||
for (FileSystemEntity file in files) {
|
||||
print('File name in photo directory : ${file.path.split('/').last}');
|
||||
}
|
||||
|
||||
// Get a list of all files in the "photos" directory
|
||||
files = await cacheDir.list().toList();
|
||||
|
||||
// Print out the names of the files in the directory
|
||||
for (FileSystemEntity file in files) {
|
||||
print('File name in cache directory : ${file.path.split('/').last}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:logger/logger.dart';
|
||||
|
||||
class LoggerUtil {
|
||||
static final Logger _logger = Logger(printer: PrettyPrinter());
|
||||
|
||||
static final Logger _loggerNoStack = Logger(
|
||||
printer: PrettyPrinter(methodCount: 0),
|
||||
);
|
||||
|
||||
static void logVerbose(String message) {
|
||||
_logger.v(message);
|
||||
}
|
||||
|
||||
static void logDebug(String message) {
|
||||
_logger.d(message);
|
||||
}
|
||||
|
||||
static void logInfo(String message) {
|
||||
_logger.i(message);
|
||||
}
|
||||
|
||||
static void logWarning(String message) {
|
||||
_logger.w(message);
|
||||
}
|
||||
|
||||
static void logError(String message) {
|
||||
_logger.e(message);
|
||||
}
|
||||
|
||||
static void logNStacktackVerbose(String message) {
|
||||
_loggerNoStack.v(message);
|
||||
}
|
||||
|
||||
static void logNStacktackDebug(String message) {
|
||||
_loggerNoStack.d(message);
|
||||
}
|
||||
|
||||
static void logNStackInfo(String message) {
|
||||
_loggerNoStack.i(message);
|
||||
}
|
||||
|
||||
static void logNStackWarning(String message) {
|
||||
_loggerNoStack.w(message);
|
||||
}
|
||||
|
||||
static void logNStackError(String message) {
|
||||
_loggerNoStack.e(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:mobdr/core/routes/plausible_tracker.dart';
|
||||
import 'package:mobdr/service/shared_prefs.dart';
|
||||
import 'package:mobdr/network/get_ip_address.dart';
|
||||
|
||||
class PlausibleUtil {
|
||||
static PlausibleTracker? plausible;
|
||||
|
||||
static Future<void> initializePlausible(double screenWidth) async {
|
||||
plausible = PlausibleTracker(
|
||||
"https://plausible.q2ii.fr",
|
||||
"mobdr.ikksgroup.com",
|
||||
);
|
||||
|
||||
await plausible!.hello();
|
||||
|
||||
plausible!.userAgent = SharedPrefs().mobileUserAgent;
|
||||
plausible!.xForwardedFor = await getPublicIPAddress();
|
||||
plausible!.screenWidth = screenWidth.toString();
|
||||
plausible!.enabled = await plausible!.hello();
|
||||
}
|
||||
|
||||
static void addEvent({
|
||||
required String name,
|
||||
required String page,
|
||||
String? referrer,
|
||||
Map<String, String>? props,
|
||||
}) {
|
||||
PlausibleUtil.plausible?.event(
|
||||
name: name,
|
||||
page: page,
|
||||
referrer: referrer ?? '',
|
||||
props: props ?? {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -117,6 +117,13 @@ class SharedPrefs {
|
|||
_sharedPrefs.setString('photosDir', value);
|
||||
}
|
||||
|
||||
/// get/set application's cache directory
|
||||
String get cacheDir => _sharedPrefs.getString('cacheDir') ?? "";
|
||||
|
||||
set cacheDir(String value) {
|
||||
_sharedPrefs.setString('cacheDir', value);
|
||||
}
|
||||
|
||||
/// get/set url MP4
|
||||
String get urlMP4 => _sharedPrefs.getString('urlMP4') ?? "";
|
||||
|
||||
|
|
@ -138,4 +145,12 @@ class SharedPrefs {
|
|||
set lastCalendarRefresh(String value) {
|
||||
_sharedPrefs.setString('key_lastCalendarRefresh', value);
|
||||
}
|
||||
|
||||
/// Get/set userAgent
|
||||
String get mobileUserAgent =>
|
||||
_sharedPrefs.getString('key_mobileUserAgent') ?? "";
|
||||
|
||||
set mobileUserAgent(String value) {
|
||||
_sharedPrefs.setString('key_mobileUserAgent', value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ import 'package:mobdr/main.dart';
|
|||
import 'package:mobdr/ui/account/tab_account.dart';
|
||||
import 'package:mobdr/ui/home/tab_home.dart';
|
||||
import 'package:mobdr/ui/mp4/tab_mp4.dart';
|
||||
import 'package:mobdr/ui/sync/synchronization.dart';
|
||||
import 'package:mobdr/ui/sync/tab_synchro.dart';
|
||||
|
||||
import 'package:mobdr/config/constant.dart';
|
||||
import 'package:mobdr/events.dart';
|
||||
import 'package:mobdr/service/plausible.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
@override
|
||||
|
|
@ -100,6 +101,8 @@ class _HomePageState extends State<HomePage>
|
|||
_pageController.jumpToPage(value);
|
||||
FocusScope.of(context).unfocus();
|
||||
});
|
||||
// analytics
|
||||
_trackPageView(value);
|
||||
}
|
||||
},
|
||||
selectedFontSize: 8,
|
||||
|
|
@ -138,4 +141,24 @@ class _HomePageState extends State<HomePage>
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _trackPageView(int index) {
|
||||
String page = '';
|
||||
switch (index) {
|
||||
case 0:
|
||||
page = 'tab_home';
|
||||
break;
|
||||
case 1:
|
||||
page = 'tab_synchro';
|
||||
break;
|
||||
case 2:
|
||||
page = 'tab_mp4';
|
||||
break;
|
||||
case 3:
|
||||
page = 'tab_account';
|
||||
break;
|
||||
}
|
||||
|
||||
PlausibleUtil.addEvent(name: 'pageview', page: page);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,369 +0,0 @@
|
|||
/*
|
||||
install plugin in pubspec.yaml
|
||||
- image_picker => to pick image from storage or camera (https://pub.dev/packages/image_picker)
|
||||
add this to ios Info.plist
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>I need this permission to test upload photo</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>I need this permission to test upload photo</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>I need this permission to test upload photo</string>
|
||||
|
||||
When using package image_picker: ^0.8.0+4, we should add this permission at AndroidManifest
|
||||
android/app/src/debug/AndroidManifest.xml | android/app/src/main/AndroidManifest.xml | android/app/src/profile/AndroidManifest.xml
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
- permission_handler => to handle permission such as storage, camera (https://pub.dev/packages/permission_handler)
|
||||
From above link, you should add this to the podfile if you are running on iOS:
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
|
||||
# You can enable the permissions needed here. For example to enable camera
|
||||
# permission, just remove the `#` character in front so it looks like this:
|
||||
#
|
||||
# ## dart: PermissionGroup.camera
|
||||
# 'PERMISSION_CAMERA=1'
|
||||
#
|
||||
# Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||
'$(inherited)',
|
||||
|
||||
## dart: PermissionGroup.calendar
|
||||
# 'PERMISSION_EVENTS=1',
|
||||
|
||||
## dart: PermissionGroup.reminders
|
||||
# 'PERMISSION_REMINDERS=1',
|
||||
|
||||
## dart: PermissionGroup.contacts
|
||||
# 'PERMISSION_CONTACTS=1',
|
||||
|
||||
## dart: PermissionGroup.camera
|
||||
'PERMISSION_CAMERA=1',
|
||||
|
||||
## dart: PermissionGroup.microphone
|
||||
# 'PERMISSION_MICROPHONE=1',
|
||||
|
||||
## dart: PermissionGroup.speech
|
||||
# 'PERMISSION_SPEECH_RECOGNIZER=1',
|
||||
|
||||
## dart: PermissionGroup.photos
|
||||
'PERMISSION_PHOTOS=1',
|
||||
|
||||
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
|
||||
# 'PERMISSION_LOCATION=1',
|
||||
|
||||
## dart: PermissionGroup.notification
|
||||
# 'PERMISSION_NOTIFICATIONS=1',
|
||||
|
||||
## dart: PermissionGroup.mediaLibrary
|
||||
# 'PERMISSION_MEDIA_LIBRARY=1',
|
||||
|
||||
## dart: PermissionGroup.sensors
|
||||
# 'PERMISSION_SENSORS=1',
|
||||
|
||||
## dart: PermissionGroup.bluetooth
|
||||
# 'PERMISSION_BLUETOOTH=1',
|
||||
|
||||
## dart: PermissionGroup.appTrackingTransparency
|
||||
# 'PERMISSION_APP_TRACKING_TRANSPARENCY=1',
|
||||
|
||||
## dart: PermissionGroup.criticalAlerts
|
||||
# 'PERMISSION_CRITICAL_ALERTS=1'
|
||||
]
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
we add some logic function so if the user press back or done with this pages, cache images will be deleted and not makes the storage full
|
||||
|
||||
Don't forget to add all images and sound used in this pages at the pubspec.yaml
|
||||
|
||||
*** IMPORTANT NOTES FOR IOS ***
|
||||
Image Picker will crash if you pick image for a second times, this error only exist on iOS Simulator 14 globaly around the world but not error on the real device
|
||||
If you want to use iOS Simulator, you need to downgrade and using iOS Simulator 13
|
||||
Follow this step to downgrade :
|
||||
1. Xcode > Preferences
|
||||
2. Select the "Components" tab.
|
||||
3. Download and select Simulator 13 after the download is finish
|
||||
4. Press "Check and Install Now".
|
||||
5. After that, use Simulator 13 instead of simulator 14
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:universal_io/io.dart' as IO;
|
||||
|
||||
import 'package:mobdr/ui/reusable/global_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:image_picker_android/image_picker_android.dart';
|
||||
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||
|
||||
class PhotoPickPage extends StatefulWidget {
|
||||
@override
|
||||
_PhotoPickPageState createState() => _PhotoPickPageState();
|
||||
}
|
||||
|
||||
class _PhotoPickPageState extends State<PhotoPickPage> {
|
||||
// initialize global widget
|
||||
final _globalWidget = GlobalWidget();
|
||||
|
||||
Color _color1 = Color(0xFF0181cc);
|
||||
Color _color2 = Color(0xff777777);
|
||||
|
||||
File? _image;
|
||||
final _picker = ImagePicker();
|
||||
|
||||
dynamic _selectedFile;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (!kIsWeb) {
|
||||
if (_selectedFile != null && _selectedFile!.existsSync()) {
|
||||
_selectedFile!.deleteSync();
|
||||
}
|
||||
}
|
||||
_selectedFile = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future requestPermission(Permission permission) async {
|
||||
final result = await permission.request();
|
||||
return result;
|
||||
}
|
||||
|
||||
void _askPermissionCamera() {
|
||||
requestPermission(Permission.camera).then(_onStatusRequestedCamera);
|
||||
}
|
||||
|
||||
void _askPermissionStorage() {
|
||||
requestPermission(Permission.storage).then(_onStatusRequested);
|
||||
}
|
||||
|
||||
void _askPermissionPhotos() {
|
||||
requestPermission(Permission.photos).then(_onStatusRequested);
|
||||
}
|
||||
|
||||
void _onStatusRequested(status) {
|
||||
if (status != PermissionStatus.granted) {
|
||||
if (IO.Platform.isIOS) {
|
||||
openAppSettings();
|
||||
} else {
|
||||
if (status == PermissionStatus.permanentlyDenied) {
|
||||
openAppSettings();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_getImage(ImageSource.gallery);
|
||||
}
|
||||
}
|
||||
|
||||
void _onStatusRequestedCamera(status) {
|
||||
if (status != PermissionStatus.granted) {
|
||||
if (IO.Platform.isIOS) {
|
||||
openAppSettings();
|
||||
} else {
|
||||
if (status == PermissionStatus.permanentlyDenied) {
|
||||
openAppSettings();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_getImage(ImageSource.camera);
|
||||
}
|
||||
}
|
||||
|
||||
void _getImage(ImageSource source) async {
|
||||
final ImagePickerPlatform imagePickerImplementation =
|
||||
ImagePickerPlatform.instance;
|
||||
if (imagePickerImplementation is ImagePickerAndroid) {
|
||||
imagePickerImplementation.useAndroidPhotoPicker = true;
|
||||
}
|
||||
final pickedFile = await _picker.pickImage(
|
||||
source: source, maxWidth: 640, imageQuality: 100);
|
||||
setState(() {
|
||||
if (pickedFile != null) {
|
||||
_image = File(pickedFile.path);
|
||||
}
|
||||
});
|
||||
|
||||
if (_image != null) {
|
||||
this.setState(() {
|
||||
if (!kIsWeb) {
|
||||
if (_selectedFile != null && _selectedFile!.existsSync()) {
|
||||
_selectedFile!.deleteSync();
|
||||
}
|
||||
}
|
||||
if (kIsWeb) {
|
||||
_selectedFile = pickedFile;
|
||||
} else {
|
||||
_selectedFile = _image;
|
||||
}
|
||||
|
||||
_image = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: _globalWidget.globalAppBar(),
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
SingleChildScrollView(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
_getImageWidget(),
|
||||
SizedBox(height: 30),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
(!kIsWeb)
|
||||
? GestureDetector(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.camera_alt,
|
||||
color: _color2,
|
||||
size: 40,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text('Camera'),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
_askPermissionCamera();
|
||||
},
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
Container(
|
||||
width: (!kIsWeb) ? 20 : 0,
|
||||
),
|
||||
GestureDetector(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.photo,
|
||||
color: _color2,
|
||||
size: 40,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text('Gallery'),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
if (kIsWeb) {
|
||||
_getImage(ImageSource.gallery);
|
||||
} else {
|
||||
if (IO.Platform.isIOS) {
|
||||
_askPermissionPhotos();
|
||||
} else {
|
||||
_askPermissionStorage();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
_buttonSave()
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget _getImageWidget() {
|
||||
if (_selectedFile != null) {
|
||||
return (kIsWeb)
|
||||
? Image.network(
|
||||
_selectedFile!.path,
|
||||
width: (kIsWeb) ? 640 : MediaQuery.of(context).size.width - 16,
|
||||
fit: BoxFit.fill,
|
||||
)
|
||||
: Image.file(
|
||||
_selectedFile!,
|
||||
width: (kIsWeb) ? 640 : MediaQuery.of(context).size.width - 16,
|
||||
fit: BoxFit.fill,
|
||||
);
|
||||
} else {
|
||||
return Image.asset(
|
||||
'assets/images/placeholder.jpg',
|
||||
width: (kIsWeb) ? 250 : MediaQuery.of(context).size.width - 16,
|
||||
fit: BoxFit.fill,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buttonSave() {
|
||||
if (_selectedFile != null) {
|
||||
return Container(
|
||||
margin: EdgeInsets.fromLTRB(0, 50, 0, 0),
|
||||
child: SizedBox(
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) => _color1,
|
||||
),
|
||||
overlayColor: MaterialStateProperty.all(Colors.transparent),
|
||||
shape: MaterialStateProperty.all(RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(3.0),
|
||||
)),
|
||||
),
|
||||
onPressed: () {
|
||||
if (_selectedFile != null) {
|
||||
if ((!kIsWeb)) {
|
||||
// if run on android or ios, should check the file exist or not
|
||||
if (_selectedFile!.existsSync()) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Click save success',
|
||||
toastLength: Toast.LENGTH_SHORT);
|
||||
}
|
||||
} else {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Click save success',
|
||||
toastLength: Toast.LENGTH_SHORT);
|
||||
}
|
||||
} else {
|
||||
Fluttertoast.showToast(
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
msg: 'File not found',
|
||||
fontSize: 13,
|
||||
toastLength: Toast.LENGTH_SHORT);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 30, vertical: 5),
|
||||
child: Text(
|
||||
'Save',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
// #docregion platform_imports
|
||||
// Import for Android features.
|
||||
import 'package:webview_flutter_android/webview_flutter_android.dart';
|
||||
// Import for iOS features.
|
||||
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
|
||||
// #enddocregion platform_imports
|
||||
|
||||
import 'package:mobdr/service/shared_prefs.dart';
|
||||
import 'package:mobdr/main.dart';
|
||||
|
|
@ -13,7 +19,7 @@ class TabMP4Page extends StatefulWidget {
|
|||
|
||||
class _TabMP4PageState extends State<TabMP4Page>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late final WebViewController controller;
|
||||
late final WebViewController _controller;
|
||||
late StreamSubscription sub;
|
||||
|
||||
int loadingPercentage = 0;
|
||||
|
|
@ -23,27 +29,76 @@ class _TabMP4PageState extends State<TabMP4Page>
|
|||
void initState() {
|
||||
super.initState();
|
||||
|
||||
controller = WebViewController()
|
||||
..setNavigationDelegate(NavigationDelegate(
|
||||
onPageStarted: (url) {
|
||||
setState(() {
|
||||
loadingPercentage = 0;
|
||||
});
|
||||
},
|
||||
onProgress: (progress) {
|
||||
setState(() {
|
||||
loadingPercentage = progress;
|
||||
});
|
||||
},
|
||||
onPageFinished: (url) {
|
||||
setState(() {
|
||||
loadingPercentage = 100;
|
||||
});
|
||||
},
|
||||
))
|
||||
..loadRequest(
|
||||
Uri.parse(url),
|
||||
// #docregion platform_features
|
||||
late final PlatformWebViewControllerCreationParams params;
|
||||
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
|
||||
params = WebKitWebViewControllerCreationParams(
|
||||
allowsInlineMediaPlayback: true,
|
||||
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
|
||||
);
|
||||
} else {
|
||||
params = const PlatformWebViewControllerCreationParams();
|
||||
}
|
||||
|
||||
final WebViewController controller =
|
||||
WebViewController.fromPlatformCreationParams(params);
|
||||
// #enddocregion platform_features
|
||||
|
||||
controller
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setBackgroundColor(const Color(0x00000000))
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onProgress: (int progress) {
|
||||
debugPrint('WebView is loading (progress : $progress%)');
|
||||
},
|
||||
onPageStarted: (String url) {
|
||||
debugPrint('Page started loading: $url');
|
||||
},
|
||||
onPageFinished: (String url) {
|
||||
debugPrint('Page finished loading: $url');
|
||||
},
|
||||
onWebResourceError: (WebResourceError error) {
|
||||
debugPrint('''
|
||||
Page resource error:
|
||||
code: ${error.errorCode}
|
||||
description: ${error.description}
|
||||
errorType: ${error.errorType}
|
||||
isForMainFrame: ${error.isForMainFrame}
|
||||
''');
|
||||
},
|
||||
onNavigationRequest: (NavigationRequest request) {
|
||||
if (request.url.startsWith('https://www.youtube.com/')) {
|
||||
debugPrint('blocking navigation to ${request.url}');
|
||||
return NavigationDecision.prevent;
|
||||
}
|
||||
debugPrint('allowing navigation to ${request.url}');
|
||||
return NavigationDecision.navigate;
|
||||
},
|
||||
onUrlChange: (UrlChange change) {
|
||||
debugPrint('url change to ${change.url}');
|
||||
},
|
||||
),
|
||||
)
|
||||
..addJavaScriptChannel(
|
||||
'Toaster',
|
||||
onMessageReceived: (JavaScriptMessage message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(message.message)),
|
||||
);
|
||||
},
|
||||
)
|
||||
..loadRequest(Uri.parse(url));
|
||||
|
||||
// #docregion platform_features
|
||||
if (controller.platform is AndroidWebViewController) {
|
||||
AndroidWebViewController.enableDebugging(true);
|
||||
(controller.platform as AndroidWebViewController)
|
||||
.setMediaPlaybackRequiresUserGesture(false);
|
||||
}
|
||||
// #enddocregion platform_features
|
||||
|
||||
_controller = controller;
|
||||
|
||||
sub = eventBus.on<UrlEvent>().listen((e) {
|
||||
setState(() {
|
||||
|
|
@ -67,7 +122,7 @@ class _TabMP4PageState extends State<TabMP4Page>
|
|||
child: Stack(
|
||||
children: [
|
||||
WebViewWidget(
|
||||
controller: controller,
|
||||
controller: _controller,
|
||||
),
|
||||
if (loadingPercentage < 100)
|
||||
LinearProgressIndicator(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:mobdr/service/shared_prefs.dart';
|
||||
import 'package:mobdr/service/plausible.dart';
|
||||
|
||||
import 'package:mobdr/config/constant.dart';
|
||||
import 'package:mobdr/ui/onboarding.dart';
|
||||
import 'package:mobdr/ui/home.dart';
|
||||
|
|
@ -28,9 +30,11 @@ class _SplashScreenPageState extends State<SplashScreenPage> {
|
|||
|
||||
if (SharedPrefs().onboarding == 0) {
|
||||
SharedPrefs().onboarding = 1;
|
||||
PlausibleUtil.addEvent(name: 'pageview', page: 'onboarding');
|
||||
Navigator.pushReplacement(context,
|
||||
MaterialPageRoute(builder: (context) => OnBoardingPage()));
|
||||
} else {
|
||||
PlausibleUtil.addEvent(name: 'pageview', page: 'tab_home');
|
||||
Navigator.pushReplacement(
|
||||
context, MaterialPageRoute(builder: (context) => HomePage()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _clearVisitPhotoCache() async {
|
||||
Future<void> _cleanVisitPhotoDir() async {
|
||||
List<VisitPhoto> _visitPhotoListTokeep;
|
||||
Directory photosDir = Directory(SharedPrefs().photosDir);
|
||||
|
||||
|
|
@ -174,6 +174,26 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _clearPhotoCache() async {
|
||||
Directory cacheDir = Directory(SharedPrefs().cacheDir);
|
||||
|
||||
// Get a list of all files in the "cache" directory
|
||||
final List<FileSystemEntity> files = await cacheDir.list().toList();
|
||||
|
||||
// Check each file in the directory
|
||||
for (FileSystemEntity file in files) {
|
||||
// Get the file name
|
||||
String fileName = file.path.split('/').last;
|
||||
|
||||
// Check if the file name starts with "CAP"
|
||||
if (fileName.startsWith("CAP")) {
|
||||
// Delete the file
|
||||
await file.delete();
|
||||
print('Deleted file: $fileName');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void startSync() async {
|
||||
// disable navigation bar buttons
|
||||
eventBus.fire(SynchronizationEvent(true));
|
||||
|
|
@ -205,7 +225,10 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
// supprimer les visites "ancienne" sans photo !!
|
||||
|
||||
// deletes photos that are no longer in any visits
|
||||
await _clearVisitPhotoCache();
|
||||
await _cleanVisitPhotoDir();
|
||||
|
||||
// deleting photos in the cache directory
|
||||
await _clearPhotoCache();
|
||||
|
||||
// last synchronization date
|
||||
SharedPrefs().lastCalendarRefresh =
|
||||
|
|
@ -234,89 +257,90 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
appBar: AppBar(
|
||||
title: Text('Data synchronization'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 20),
|
||||
Center(
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _isInternetConnexion
|
||||
? Colors.blue
|
||||
: Colors
|
||||
.red, // Modifier la couleur en fonction de la connexion Internet
|
||||
),
|
||||
child: IconButton(
|
||||
icon: AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Transform.rotate(
|
||||
angle: _rotationAnimation.value * 2.0 * 3.14,
|
||||
child: Icon(
|
||||
_isInternetConnexion
|
||||
? Icons.refresh
|
||||
: Icons
|
||||
.signal_wifi_off, // Modifier l'icône en fonction de la connexion Internet
|
||||
color: Colors.white,
|
||||
size: 70,
|
||||
),
|
||||
);
|
||||
},
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _isInternetConnexion
|
||||
? Colors.blue
|
||||
: Colors
|
||||
.red, // Modifier la couleur en fonction de la connexion Internet
|
||||
),
|
||||
child: IconButton(
|
||||
icon: AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Transform.rotate(
|
||||
angle: _rotationAnimation.value * 2.0 * 3.14,
|
||||
child: Icon(
|
||||
_isInternetConnexion
|
||||
? Icons.refresh
|
||||
: Icons
|
||||
.signal_wifi_off, // Modifier l'icône en fonction de la connexion Internet
|
||||
color: Colors.white,
|
||||
size: 70,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onPressed: _isSyncing ? null : doSync,
|
||||
),
|
||||
onPressed: _isSyncing ? null : doSync,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
!_isInternetConnexion
|
||||
? "No internet connection ..."
|
||||
: (SharedPrefs().lastCalendarRefresh.isNotEmpty
|
||||
? SharedPrefs().lastCalendarRefresh
|
||||
: "Never"),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: !_isInternetConnexion
|
||||
? Colors.red
|
||||
: Colors
|
||||
.black, // Modifier la couleur du texte en fonction de votre préférence
|
||||
fontWeight: FontWeight.bold,
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
!_isInternetConnexion
|
||||
? "No internet connection ..."
|
||||
: (SharedPrefs().lastCalendarRefresh.isNotEmpty
|
||||
? SharedPrefs().lastCalendarRefresh
|
||||
: "Never"),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: !_isInternetConnexion
|
||||
? Colors.red
|
||||
: Colors
|
||||
.black, // Modifier la couleur du texte en fonction de votre préférence
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
SyncItem(
|
||||
icon: Icons.business,
|
||||
title: 'Backoffice',
|
||||
description: 'Synchronisation des données du backoffice',
|
||||
isSyncing: _isSyncing,
|
||||
syncCompleted: _syncCompleted,
|
||||
isError: _syncCompleted && !_backofficeSyncCompleted,
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
SyncItem(
|
||||
icon: Icons.photo,
|
||||
title: 'Photos',
|
||||
description: 'Synchronisation des photos',
|
||||
isSyncing: _isSyncing,
|
||||
syncCompleted: _syncCompleted,
|
||||
isError: _syncCompleted && !_photosSyncCompleted,
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
SyncItem(
|
||||
icon: Icons.warning,
|
||||
title: 'Log',
|
||||
description: 'Synchronisation des journaux d\'activité',
|
||||
isSyncing: _isSyncing,
|
||||
syncCompleted: _syncCompleted,
|
||||
isError: _syncCompleted && !_logSyncCompleted,
|
||||
),
|
||||
],
|
||||
SizedBox(height: 30),
|
||||
SyncItem(
|
||||
icon: Icons.business,
|
||||
title: 'Backoffice',
|
||||
description: 'Synchronisation des données du backoffice',
|
||||
isSyncing: _isSyncing,
|
||||
syncCompleted: _syncCompleted,
|
||||
isError: _syncCompleted && !_backofficeSyncCompleted,
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
SyncItem(
|
||||
icon: Icons.photo,
|
||||
title: 'Photos',
|
||||
description: 'Synchronisation des photos',
|
||||
isSyncing: _isSyncing,
|
||||
syncCompleted: _syncCompleted,
|
||||
isError: _syncCompleted && !_photosSyncCompleted,
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
SyncItem(
|
||||
icon: Icons.warning,
|
||||
title: 'Log',
|
||||
description: 'Synchronisation des journaux d\'activité',
|
||||
isSyncing: _isSyncing,
|
||||
syncCompleted: _syncCompleted,
|
||||
isError: _syncCompleted && !_logSyncCompleted,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -34,8 +34,6 @@ class _VisitPhotoTypologyPageState extends State<VisitPhotoTypologyPage> {
|
|||
));
|
||||
Navigator.push(context, route).then(onGoBack);
|
||||
},
|
||||
|
||||
/// TODO objectbox.noteBox.remove(PhotoTypology[index].id),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: Colors.black12)),
|
||||
|
|
|
|||
|
|
@ -19,12 +19,10 @@ import 'package:mobdr/main.dart';
|
|||
import 'package:mobdr/db/box_visit_photo.dart';
|
||||
import 'package:mobdr/model/visit_model.dart';
|
||||
import 'package:mobdr/ui/reusable/global_widget.dart';
|
||||
import 'package:mobdr/ui/home/photo_camera.dart';
|
||||
import 'package:mobdr/ui/visit/photo_camera.dart';
|
||||
import 'package:mobdr/ui/visit/visit_photo_typology_detail.dart';
|
||||
import 'package:mobdr/events.dart';
|
||||
|
||||
// TODO Il faut supprimer les possibles photos du répertoire cache !
|
||||
|
||||
extension FileNameExtension on File {
|
||||
String getFileName() {
|
||||
String fileName = path.split('/').last;
|
||||
|
|
|
|||
22
pubspec.lock
22
pubspec.lock
|
|
@ -157,18 +157,18 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: camera
|
||||
sha256: "7afc256902062cab191540c09908b98bc71e93d5e20b6486dbee51aa7731e9b2"
|
||||
sha256: ebebead3d5ec3d148249331d751d462d7e8c98102b8830a9b45ec96a2bd4333f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.4"
|
||||
version: "0.10.5+2"
|
||||
camera_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_android
|
||||
sha256: "772c111c78f31f868b98dbf6dbeda8d6ff77acea773a92ea5705ee2f7949ebfb"
|
||||
sha256: f83e406d34f5faa80bf0f5c3beee4b4c11da94a94e9621c1bb8e312988621b4b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.5"
|
||||
version: "0.10.8+2"
|
||||
camera_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -181,10 +181,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: camera_platform_interface
|
||||
sha256: "00d972adee2e8a282b4d7445e8e694aa1dc0c36b70455b99afa96fbf5e814119"
|
||||
sha256: "60fa0bb62a4f3bf3a7c413e31e4cd01b69c779ccc8e4668904a24581b86c316b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.5.1"
|
||||
camera_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -245,10 +245,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: b74247fad72c171381dbe700ca17da24deac637ab6d43c343b42867acb95c991
|
||||
sha256: "8599ae9edca5ff96163fca3e36f8e481ea917d1e71cdad912c084b5579913f34"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "4.0.1"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -622,7 +622,7 @@ packages:
|
|||
source: hosted
|
||||
version: "4.8.0"
|
||||
logger:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logger
|
||||
sha256: db2ff852ed77090ba9f62d3611e4208a3d11dfa35991a81ae724c113fcb3e3f7
|
||||
|
|
@ -1254,10 +1254,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: "1a37bdbaaf5fbe09ad8579ab09ecfd473284ce482f900b5aea27cf834386a567"
|
||||
sha256: "5604dac1178680a34fbe4a08c7b69ec42cca6601dc300009ec9ff69bef284cc2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
version: "4.2.1"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
22
pubspec.yaml
22
pubspec.yaml
|
|
@ -44,20 +44,27 @@ dependencies:
|
|||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: 1.0.5
|
||||
fluttertoast: 8.1.3
|
||||
|
||||
package_info_plus: ^3.0.3
|
||||
|
||||
flutter_bloc: 8.1.2
|
||||
|
||||
flutter_html: 3.0.0-alpha.6
|
||||
|
||||
# https://pub.dev/packages/wakelock
|
||||
wakelock: ^0.6.2
|
||||
|
||||
# https://pub.dev/packages/
|
||||
shimmer: 2.0.0
|
||||
|
||||
# https://pub.dev/packages/image_picker
|
||||
image_picker: ^0.8.7+4
|
||||
|
||||
# https://pub.dev/packages/camera
|
||||
camera: ^0.10.4
|
||||
camera: ^0.10.5+2
|
||||
|
||||
permission_handler: 10.2.0
|
||||
|
||||
image: ^4.0.15
|
||||
|
||||
# https://pub.dev/packages/super_tag_editor
|
||||
|
|
@ -69,21 +76,25 @@ dependencies:
|
|||
intl: 0.17.0
|
||||
|
||||
carousel_slider: 4.2.1
|
||||
|
||||
cached_network_image: 3.2.3
|
||||
|
||||
get_it: ^7.2.0
|
||||
|
||||
# https://pub.dev/packages/shared_preferences
|
||||
shared_preferences: ^2.1.0
|
||||
|
||||
timelines: ^0.1.0
|
||||
|
||||
universal_io: 2.2.0
|
||||
|
||||
xml: ^6.2.2
|
||||
|
||||
# https://pub.dev/packages/device_info_plus/
|
||||
device_info_plus: ^8.2.0
|
||||
|
||||
# https://pub.dev/packages/webview_flutter/
|
||||
webview_flutter: ^4.2.0
|
||||
webview_flutter: ^4.2.1
|
||||
|
||||
# https://pub.dev/packages/event_bus_plus/
|
||||
event_bus_plus: ^0.6.1
|
||||
|
|
@ -92,8 +103,11 @@ dependencies:
|
|||
badges: ^3.1.1
|
||||
|
||||
# https://pub.dev/packages/connectivity_plus
|
||||
connectivity_plus: ^3.0.6
|
||||
|
||||
connectivity_plus: ^4.0.1
|
||||
|
||||
# https://pub.dev/packages/logger
|
||||
logger: ^1.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
|
|
|||
Loading…
Reference in New Issue