refactor:log, analytics, main

release/mobdr-v0.0.1
Frédérik Benoist 2023-05-31 00:00:39 +02:00
parent d01f8e84b4
commit 37cd9272e7
21 changed files with 777 additions and 730 deletions

View File

@ -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

View File

@ -1,6 +0,0 @@
abstract class Routes {
static const INITIAL = '/';
static const LOGIN = '/login';
static const CAMERA = '/camera';
static const LIST = '/list';
}

View File

@ -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;
}
}

8
lib/db/db_log.dart Normal file
View File

@ -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);
}
}

View File

@ -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,
};
}
}

View File

@ -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';
}

View File

@ -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';
}
}

View File

@ -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}');
}
}
}

View File

@ -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);
}
}

View File

@ -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 ?? {},
);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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(

View File

@ -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()));
}

View File

@ -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,
),
],
),
),
),
);

View File

@ -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)),

View File

@ -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;

View File

@ -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:

View File

@ -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