From f69e6a70d7226c746c31360fba5f62aa1088eda8 Mon Sep 17 00:00:00 2001 From: canewsin Date: Sun, 20 Sep 2020 15:35:46 +0530 Subject: [PATCH] v 0.7.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Major UI Changes with Modern UI 😍😍. - Faster Startup of ZeroNet 😁😁. - Individual Site Details for Popular Sites with Stats and useful functions 😘😘. - Update Minimum Android Support Version to API 21 (Android 5.0 Lollipop). - Add Site Shortcut to HomeScreen. - Added In-App Review so that you can give your feedback quickly. - Several Bug Fixes and Improvements. squash merge from this commit https://github.com/canewsin/zeronet_mobile/commit/e7601a6f0233632c854b45827cdc06724f6d192f to master --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 3 +- .../src/main/jniLibs/arm64-v8a/libopenssl.so | Bin .../main/jniLibs/arm64-v8a/libpython3.8.so | Bin .../src/main/jniLibs/arm64-v8a/libtor.so | Bin .../main/jniLibs/armeabi-v7a/libopenssl.so | Bin .../main/jniLibs/armeabi-v7a/libpython3.8.so | Bin .../src/main/jniLibs/armeabi-v7a/libtor.so | Bin .../src/main/jniLibs/x86_64/libopenssl.so | Bin .../src/main/jniLibs/x86_64/libpython3.8.so | Bin .../src/main/jniLibs/x86_64/libtor.so | Bin .../kotlin/in/canews/zeronet/MainActivity.kt | 85 ++- .../kotlin/in/canews/zeronet/MyApplication.kt | 2 +- android/nativelibs/.gitignore | 1 - android/nativelibs/build.gradle | 26 - .../nativelibs/src/main/AndroidManifest.xml | 14 - android/version.properties | 4 +- lib/core/site/site.dart | 148 ++++ lib/core/site/site_manager.dart | 21 + lib/core/user/user.dart | 189 +++++ lib/core/user/user_manager.dart | 15 + lib/main.dart | 87 ++- lib/mobx/uistore.dart | 103 +++ lib/mobx/uistore.g.dart | 256 +++++++ lib/mobx/varstore.dart | 12 +- lib/mobx/varstore.g.dart | 27 - lib/models/enums.dart | 192 +++++ lib/models/models.dart | 122 ++-- lib/others/common.dart | 19 +- lib/others/constants.dart | 357 ++++++++- lib/others/extensions.dart | 17 + lib/others/native.dart | 39 +- lib/others/zeronet_utils.dart | 111 ++- lib/widgets/common.dart | 194 +++++ lib/widgets/home_page.dart | 682 ++++++++++++++++++ lib/widgets/log_page.dart | 34 + lib/widgets/my_app.dart | 26 - lib/widgets/myhome_page.dart | 509 ------------- lib/widgets/settings_page.dart | 649 +++++++---------- lib/widgets/shortcut_loading_page.dart | 60 ++ lib/widgets/zerobrowser_page.dart | 183 +++++ pubspec.yaml | 10 +- test/test.dart | 11 - test/widget_test.dart | 40 - 44 files changed, 3091 insertions(+), 1159 deletions(-) rename android/{nativelibs => app}/src/main/jniLibs/arm64-v8a/libopenssl.so (100%) rename android/{nativelibs => app}/src/main/jniLibs/arm64-v8a/libpython3.8.so (100%) rename android/{nativelibs => app}/src/main/jniLibs/arm64-v8a/libtor.so (100%) rename android/{nativelibs => app}/src/main/jniLibs/armeabi-v7a/libopenssl.so (100%) rename android/{nativelibs => app}/src/main/jniLibs/armeabi-v7a/libpython3.8.so (100%) rename android/{nativelibs => app}/src/main/jniLibs/armeabi-v7a/libtor.so (100%) rename android/{nativelibs => app}/src/main/jniLibs/x86_64/libopenssl.so (100%) rename android/{nativelibs => app}/src/main/jniLibs/x86_64/libpython3.8.so (100%) rename android/{nativelibs => app}/src/main/jniLibs/x86_64/libtor.so (100%) delete mode 100644 android/nativelibs/.gitignore delete mode 100644 android/nativelibs/build.gradle delete mode 100644 android/nativelibs/src/main/AndroidManifest.xml create mode 100644 lib/core/site/site.dart create mode 100644 lib/core/site/site_manager.dart create mode 100644 lib/core/user/user.dart create mode 100644 lib/core/user/user_manager.dart create mode 100644 lib/mobx/uistore.dart create mode 100644 lib/mobx/uistore.g.dart create mode 100644 lib/models/enums.dart create mode 100644 lib/widgets/common.dart create mode 100644 lib/widgets/home_page.dart create mode 100644 lib/widgets/log_page.dart delete mode 100644 lib/widgets/my_app.dart delete mode 100644 lib/widgets/myhome_page.dart create mode 100644 lib/widgets/shortcut_loading_page.dart create mode 100644 lib/widgets/zerobrowser_page.dart delete mode 100644 test/test.dart delete mode 100644 test/widget_test.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 2d2a244..6f2550f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -83,7 +83,7 @@ android { defaultConfig.ndk.abiFilters 'armeabi-v7a','arm64-v8a','x86_64' } } - dynamicFeatures = [":arm64", ":arm", ":common", ":x86", ":x86_64", ":nativelibs"] + dynamicFeatures = [":arm64", ":arm", ":common", ":x86", ":x86_64"] } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d3b8a0b..e427283 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -11,8 +11,9 @@ + - + when (call.method) { + "addToHomeScreen" -> addShortcutToHomeScreen(context, result, + call.argument("title"),call.argument("url"), + call.argument("logoPath") + ) "batteryOptimisations" -> getBatteryOptimizations(result) + "copyAssetsToCache" -> result.success(copyAssetsToCache()) + "getAppInstallTime" -> getAppInstallTime(result) + "getAppLastUpdateTime" -> getAppLastUpdateTime(result) "isBatteryOptimized" -> isBatteryOptimized(result) "isPlayStoreInstall" -> result.success(isPlayStoreInstall(this)) "initSplitInstall" -> { @@ -57,23 +76,23 @@ class MainActivity : FlutterActivity() { splitInstallManager = LocallyDynamicSplitInstallManagerFactory.create(this) result.success(true) } - "uninstallModules" -> uninstallModules() "isModuleInstallSupported" -> result.success(isModuleInstallSupported()) "isRequiredModulesInstalled" -> result.success(isRequiredModulesInstalled()) - "copyAssetsToCache" -> result.success(copyAssetsToCache()) + "launchZiteUrl" -> result.success(mLaunchShortcutUrl) + "moveTaskToBack" -> { + moveTaskToBack(true) + result.success(true) + } "nativeDir" -> result.success(applicationInfo.nativeLibraryDir) + "nativePrint" -> { + Log.e("Flutter>nativePrint()",call.arguments()) + } "openJsonFile" -> openJsonFile(result) "openZipFile" -> openZipFile(result) "readJsonFromUri" -> readJsonFromUri(call.arguments.toString(), result) "readZipFromUri" -> readZipFromUri(call.arguments.toString(), result) "saveUserJsonFile" -> saveUserJsonFile(this, call.arguments.toString(), result) - "nativePrint" -> { - Log.e("Flutter>nativePrint()",call.arguments()) - } - "moveTaskToBack" -> { - moveTaskToBack(true) - result.success(true) - } + "uninstallModules" -> uninstallModules() } } } @@ -95,6 +114,54 @@ class MainActivity : FlutterActivity() { ) } + private fun addShortcutToHomeScreen(context: Context,mResult: MethodChannel.Result, + title:String?,url:String?,logoPath:String?) { + if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { + val shortcutInfoBuilder: ShortcutInfoCompat.Builder = ShortcutInfoCompat.Builder(context, title.toString()) + .setIntent( + Intent(context, MainActivity::class.java) + .setAction(Intent.ACTION_MAIN) + .putExtra( + "LAUNCH_SHORTCUT_URL", + url + ) + ) + .setShortLabel(title.toString()) + if (logoPath.toString().isNotEmpty()){ + val image = File(logoPath.toString()) + val bmOptions: BitmapFactory.Options = BitmapFactory.Options() + val bitmap: Bitmap = BitmapFactory.decodeFile(image.absolutePath, bmOptions) + shortcutInfoBuilder.setIcon(IconCompat.createWithBitmap(bitmap)) + } else { + shortcutInfoBuilder.setIcon(IconCompat.createWithResource(context, R.drawable.logo)) + } + val shortcutInfo: ShortcutInfoCompat = shortcutInfoBuilder.build() + val shortcutCallbackIntent: PendingIntent = PendingIntent.getBroadcast(context, + 0, + Intent(context, MainActivity::class.java) + .putExtra("SHORTCUT_ADDED",true), + PendingIntent.FLAG_UPDATE_CURRENT) + ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, shortcutCallbackIntent.intentSender) + mResult.success(true) + } else { + // Shortcut is not supported by your launcher + } + } + + private fun getAppInstallTime(result: MethodChannel.Result) { + val info = context.packageManager.getPackageInfo(context.packageName,0); + val field = PackageInfo::class.java.getField("firstInstallTime") + val timeStamp = field.getLong(info) + result.success(timeStamp.toString()) + } + + private fun getAppLastUpdateTime(result: MethodChannel.Result) { + val info = context.packageManager.getPackageInfo(context.packageName,0); + val field = PackageInfo::class.java.getField("lastUpdateTime") + val timeStamp = field.getLong(info) + result.success(timeStamp.toString()) + } + private fun isPlayStoreInstall(context: Context): Boolean { val validInstallers: List = listOf("com.android.vending", "com.google.android.feedback") val installer = context.packageManager.getInstallerPackageName(context.packageName) diff --git a/android/app/src/main/kotlin/in/canews/zeronet/MyApplication.kt b/android/app/src/main/kotlin/in/canews/zeronet/MyApplication.kt index 4ed484f..632749a 100644 --- a/android/app/src/main/kotlin/in/canews/zeronet/MyApplication.kt +++ b/android/app/src/main/kotlin/in/canews/zeronet/MyApplication.kt @@ -9,6 +9,6 @@ import com.google.android.play.core.splitcompat.SplitCompatApplication internal class MyApplication : SplitCompatApplication(){ override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) - SplitCompat.install(base) + SplitCompat.install(base!!) } } diff --git a/android/nativelibs/.gitignore b/android/nativelibs/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/android/nativelibs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/android/nativelibs/build.gradle b/android/nativelibs/build.gradle deleted file mode 100644 index 6bc92c8..0000000 --- a/android/nativelibs/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -apply plugin: 'com.android.dynamic-feature' - -android { - compileSdkVersion 29 - - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 29 - versionCode 1 - versionName "1.0" - - } - - compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 - } - - -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation project(':app') -} diff --git a/android/nativelibs/src/main/AndroidManifest.xml b/android/nativelibs/src/main/AndroidManifest.xml deleted file mode 100644 index 7c21bee..0000000 --- a/android/nativelibs/src/main/AndroidManifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - diff --git a/android/version.properties b/android/version.properties index bae7d30..104ba2d 100644 --- a/android/version.properties +++ b/android/version.properties @@ -1,2 +1,2 @@ -flutter.versionName=0.6.5 -flutter.versionCode=4083 +flutter.versionName=0.7.0 +flutter.versionCode=4100 diff --git a/lib/core/site/site.dart b/lib/core/site/site.dart new file mode 100644 index 0000000..c5c5f86 --- /dev/null +++ b/lib/core/site/site.dart @@ -0,0 +1,148 @@ +import 'dart:convert'; + +import 'dart:io'; + +class Site { + String address; + String addressHash; + String addressSha1; + String addressShort; + int added; + String ajaxKey; + String authKey; + int bytesRecv; + int bytesSent; + Cache cache; + int downloaded; + int modified; + int optionalDownloaded; + Map optionalHelp; + bool own; + int peers; + List permissions; + bool serving; + int size; + int sizeFilesOptional; + int sizeOptional; + String wrapperKey; + + Site({ + this.address, + this.addressHash, + this.addressSha1, + this.addressShort, + this.added, + this.ajaxKey, + this.authKey, + this.bytesRecv, + this.bytesSent, + this.cache, + this.downloaded, + this.modified, + this.optionalDownloaded, + this.optionalHelp, + this.own = false, + this.peers, + this.permissions, + this.serving = true, + this.size, + this.sizeFilesOptional, + this.sizeOptional, + this.wrapperKey, + }); + + Site pause() { + return this..serving = false; + } + + Site resume() { + return this..serving = true; + } + + Site.fromJson(Map jsonStr) { + added = jsonStr['added']?.toInt(); + ajaxKey = jsonStr['ajax_key']; + authKey = jsonStr['auth_key']; + bytesRecv = jsonStr['bytes_recv']; + bytesSent = jsonStr['bytes_sent']; + cache = jsonStr['cache'] != null ? Cache.fromJson(jsonStr['cache']) : null; + downloaded = jsonStr['downloaded']?.toInt(); + modified = jsonStr['modified']?.toInt(); + optionalDownloaded = jsonStr['optional_downloaded']; + optionalHelp = jsonStr['optional_help'] != null + ? json.decode(jsonStr['optional_help']) + : null; + own = jsonStr['own']; + peers = jsonStr['peers']; + permissions = jsonStr['permissions'].cast(); + serving = jsonStr['serving']; + size = jsonStr['size']; + sizeFilesOptional = jsonStr['size_files_optional']; + sizeOptional = jsonStr['size_optional']; + wrapperKey = jsonStr['wrapper_key']; + } + + Map toJson() { + final Map data = new Map(); + data['added'] = this.added; + data['ajax_key'] = this.ajaxKey; + data['auth_key'] = this.authKey; + data['bytes_recv'] = this.bytesRecv; + data['bytes_sent'] = this.bytesSent; + if (this.cache != null) { + data['cache'] = this.cache.toJson(); + } + data['downloaded'] = this.downloaded; + data['modified'] = this.modified; + data['optional_downloaded'] = this.optionalDownloaded; + if (this.optionalHelp != null) { + data['optional_help'] = json.encode(this.optionalHelp); + } + data['own'] = this.own; + data['peers'] = this.peers; + data['permissions'] = this.permissions; + data['serving'] = this.serving; + data['size'] = this.size; + data['size_files_optional'] = this.sizeFilesOptional; + data['size_optional'] = this.sizeOptional; + data['wrapper_key'] = this.wrapperKey; + return data; + } +} + +class Cache { + Map badFiles; + dynamic hashfield; + Map piecefields; + + Cache({ + this.badFiles, + this.hashfield, + this.piecefields, + }); + + Cache.fromJson(Map jsonStr) { + badFiles = jsonStr['bad_files'] != null ? jsonStr['bad_files'] : null; + hashfield = jsonStr['hashfield']; + piecefields = + jsonStr['piecefields'] != null ? jsonStr['piecefields'] : null; + } + + Map toJson() { + final Map data = new Map(); + if (this.badFiles != null) { + data['bad_files'] = this.badFiles; + } + data['hashfield'] = this.hashfield; + if (this.piecefields != null) { + data['piecefields'] = this.piecefields; + } + return data; + } +} + +extension SiteFileSystemExt on Site { + Site fromFile(File file) { + return Site.fromJson(json.decode(file.readAsStringSync())); + } +} diff --git a/lib/core/site/site_manager.dart b/lib/core/site/site_manager.dart new file mode 100644 index 0000000..e730bde --- /dev/null +++ b/lib/core/site/site_manager.dart @@ -0,0 +1,21 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:zeronet/core/site/site.dart'; + +class SiteManager { + Map loadSitesFromFile(File file) { + Map content = json.decode(file.readAsStringSync()); + Map sites = {}; + for (var item in content.keys) { + sites[item] = Site.fromJson(content[item]); + } + return sites; + } + + void saveSettingstoFile(File sitesFile, Map sitesData) { + if (sitesFile.existsSync()) { + sitesFile.writeAsStringSync(json.encode(sitesData)); + } + } +} diff --git a/lib/core/user/user.dart b/lib/core/user/user.dart new file mode 100644 index 0000000..c3a4bde --- /dev/null +++ b/lib/core/user/user.dart @@ -0,0 +1,189 @@ +import 'dart:convert'; + +class User { + Map certsMap; + String masterSeed; + Settings settings; + Map sites; + + User({this.certsMap, this.masterSeed, this.settings, this.sites}); + + User.fromJson(Map json) { + certsMap = {}; + if (json['certs'] != null) { + for (var cert in (json['certs'] as Map).keys) { + certsMap[cert] = Cert.fromJson((json['certs'] as Map)[cert]); + } + } + masterSeed = json['master_seed']; + settings = json['settings'] != null + ? new Settings.fromJson(json['settings']) + : null; + sites = {}; + if (json['sites'] != null) { + for (var site in (json['sites'] as Map).keys) { + sites[site] = UserSite.fromJson(json['sites'][site]); + } + } + } + + Map toJson() { + final Map data = new Map(); + if (this.certsMap != null) { + data['certs'] = json.encode(this.certsMap); + } + data['master_seed'] = this.masterSeed; + if (this.settings != null) { + data['settings'] = this.settings.toJson(); + } + if (this.sites != null) { + data['sites'] = json.encode(this.sites); + } + return data; + } +} + +class Settings { + String theme; + bool useSystemTheme; + + Settings({this.theme, this.useSystemTheme}); + + Settings.fromJson(Map json) { + theme = json['theme']; + useSystemTheme = json['use_system_theme']; + } + + Map toJson() { + final Map data = new Map(); + data['theme'] = this.theme; + data['use_system_theme'] = this.useSystemTheme; + return data; + } +} + +class Cert { + String authAddress; + String authPrivatekey; + String authType; + String authUserName; + String certSign; + + Cert( + {this.authAddress, + this.authPrivatekey, + this.authType, + this.authUserName, + this.certSign}); + + Cert.fromJson(Map json) { + authAddress = json['auth_address']; + authPrivatekey = json['auth_privatekey']; + authType = json['auth_type']; + authUserName = json['auth_user_name']; + certSign = json['cert_sign']; + } + + Map toJson() { + final Map data = new Map(); + data['auth_address'] = this.authAddress; + data['auth_privatekey'] = this.authPrivatekey; + data['auth_type'] = this.authType; + data['auth_user_name'] = this.authUserName; + data['cert_sign'] = this.certSign; + return data; + } +} + +class UserSite { + String authAddress; + String authPrivatekey; + String encryptPrivatekey0; + String encryptPublickey0; + String cert; + Settings settings; + Map follow; + String privatekey; + + UserSite( + {this.authAddress, + this.authPrivatekey, + this.encryptPrivatekey0, + this.encryptPublickey0, + this.cert, + this.settings, + this.follow, + this.privatekey}); + + UserSite.fromJson(Map json) { + authAddress = json['auth_address']; + authPrivatekey = json['auth_privatekey']; + encryptPrivatekey0 = json['encrypt_privatekey_0']; + encryptPublickey0 = json['encrypt_publickey_0']; + cert = json['cert']; + settings = json['settings'] != null + ? new Settings.fromJson(json['settings']) + : null; + follow = json['follow'] != null ? json['follow'] : null; + privatekey = json['privatekey']; + } + + Map toJson() { + final Map data = new Map(); + data['auth_address'] = this.authAddress; + data['auth_privatekey'] = this.authPrivatekey; + data['encrypt_privatekey_0'] = this.encryptPrivatekey0; + data['encrypt_publickey_0'] = this.encryptPublickey0; + data['cert'] = this.cert; + if (this.settings != null) { + data['settings'] = this.settings.toJson(); + } + if (this.follow != null) { + data['follow'] = json.encode(this.follow); + } + data['privatekey'] = this.privatekey; + return data; + } +} + +class SiteSettings { + int dateFeedVisit; + Map favoriteSites; + Map siteblocksIgnore; + String sitesOrderby; + Map sitesSectionHide; + + SiteSettings( + {this.dateFeedVisit, + this.favoriteSites, + this.siteblocksIgnore, + this.sitesOrderby, + this.sitesSectionHide}); + + SiteSettings.fromJson(Map json) { + dateFeedVisit = json['date_feed_visit']; + favoriteSites = + json['favorite_sites'] != null ? json['favorite_sites'] : null; + siteblocksIgnore = + json['siteblocks_ignore'] != null ? json['siteblocks_ignore'] : null; + sitesOrderby = json['sites_orderby']; + sitesSectionHide = + json['sites_section_hide'] != null ? json['sites_section_hide'] : null; + } + + Map toJson() { + final Map data = new Map(); + data['date_feed_visit'] = this.dateFeedVisit; + if (this.favoriteSites != null) { + data['favorite_sites'] = json.encode(this.favoriteSites); + } + if (this.siteblocksIgnore != null) { + data['siteblocks_ignore'] = json.encode(this.siteblocksIgnore); + } + data['sites_orderby'] = this.sitesOrderby; + if (this.sitesSectionHide != null) { + data['sites_section_hide'] = json.encode(this.sitesSectionHide); + } + return data; + } +} diff --git a/lib/core/user/user_manager.dart b/lib/core/user/user_manager.dart new file mode 100644 index 0000000..b34de33 --- /dev/null +++ b/lib/core/user/user_manager.dart @@ -0,0 +1,15 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:zeronet/core/user/user.dart'; + +class UserManager { + List loadUsersFromFile(File file) { + List users = []; + Map usersFileData = json.decode(file.readAsStringSync()); + for (var user in usersFileData.keys) { + users.add(User.fromJson(usersFileData[user])); + } + return users; + } +} diff --git a/lib/main.dart b/lib/main.dart index 64a6264..49da717 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,97 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:zeronet/mobx/uistore.dart'; +import 'package:zeronet/mobx/varstore.dart'; +import 'package:zeronet/others/native.dart'; +import 'package:zeronet/widgets/home_page.dart'; +import 'package:zeronet/widgets/loading_page.dart'; +import 'package:zeronet/widgets/log_page.dart'; +import 'package:zeronet/widgets/settings_page.dart'; +import 'package:zeronet/widgets/shortcut_loading_page.dart'; +import 'package:zeronet/widgets/zerobrowser_page.dart'; +import 'models/enums.dart'; import 'others/common.dart'; -import 'widgets/my_app.dart'; +import 'others/utils.dart'; +import 'others/zeronet_utils.dart'; //TODO:Remainder: Removed Half baked x86 bins, add them when we support x86 platform Future main() async { WidgetsFlutterBinding.ensureInitialized(); await init(); + launchUrl = await launchZiteUrl(); runApp(MyApp()); } + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarColor: Colors.white, + systemNavigationBarColor: Colors.white, + statusBarIconBrightness: Brightness.dark, + systemNavigationBarIconBrightness: Brightness.dark, + ), + ); + return MaterialApp( + title: 'ZeroNet Mobile', + theme: ThemeData( + primarySwatch: Colors.indigo, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + debugShowCheckedModeBanner: false, + home: Scaffold( + body: Observer( + builder: (context) { + if (varStore.zeroNetInstalled) { + if (firstTime) { + uiStore.updateCurrentAppRoute(AppRoute.Settings); + makeExecHelper(); + } + if (uiStore.zeroNetStatus == ZeroNetStatus.NOT_RUNNING) { + checkInitStatus(); + } + if (launchUrl.isNotEmpty) { + browserUrl = (zeroNetUrl.isEmpty + ? "http://127.0.0.1:43110/" + : zeroNetUrl) + + launchUrl; + if (uiStore.zeroNetStatus == ZeroNetStatus.RUNNING) { + uiStore.updateCurrentAppRoute(AppRoute.ZeroBrowser); + } else + uiStore.updateCurrentAppRoute(AppRoute.ShortcutLoadingPage); + } + return Observer( + builder: (ctx) { + switch (uiStore.currentAppRoute) { + case AppRoute.Home: + return HomePage(); + break; + case AppRoute.Settings: + return SettingsPage(); + break; + case AppRoute.ShortcutLoadingPage: + return ShortcutLoadingPage(); + break; + case AppRoute.ZeroBrowser: + return ZeroBrowser(); + break; + case AppRoute.LogPage: + return ZeroNetLogPage(); + break; + default: + return Container(); + } + }, + ); + } else + return Loading(); + }, + ), + ), + ); + } +} diff --git a/lib/mobx/uistore.dart b/lib/mobx/uistore.dart new file mode 100644 index 0000000..4a91428 --- /dev/null +++ b/lib/mobx/uistore.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:mobx/mobx.dart'; +import 'package:outline_material_icons/outline_material_icons.dart'; +import 'package:zeronet/models/enums.dart'; +import 'package:zeronet/models/models.dart'; + +// Include generated file +part 'uistore.g.dart'; + +// This is the class used by rest of your codebase +final uiStore = UiStore(); + +class UiStore = _UiStore with _$UiStore; + +// The store-class +abstract class _UiStore with Store { + PersistentBottomSheetController currentBottomSheetController; + + @observable + AppUpdate appUpdate = AppUpdate.NOT_AVAILABLE; + + @action + void updateInAppUpdateAvailable(AppUpdate available) => appUpdate = available; + + @observable + bool showSnackReply = false; + + @action + void updateShowSnackReply(bool show) { + showSnackReply = show; + } + + @observable + int reload = 0; + + @action + void updateReload(int i) { + reload = i; + } + + @observable + SiteInfo currentSiteInfo = SiteInfo(); + + @action + void updateCurrentSiteInfo(SiteInfo siteInfo) { + currentSiteInfo = siteInfo; + } + + @observable + AppRoute currentAppRoute = AppRoute.Home; + + @action + void updateCurrentAppRoute(AppRoute appRoute) { + currentAppRoute = appRoute; + switch (appRoute) { + case AppRoute.Home: + updateAppBarTitle(AppRoute.Home.title); + updateAppBarIcon(OMIcons.settings); + break; + case AppRoute.Settings: + updateAppBarTitle(AppRoute.Settings.title); + updateAppBarIcon(OMIcons.home); + break; + case AppRoute.ZeroBrowser: + updateAppBarTitle(AppRoute.ZeroBrowser.title); + updateAppBarIcon(OMIcons.home); + break; + case AppRoute.LogPage: + updateAppBarTitle(AppRoute.LogPage.title); + updateAppBarIcon(OMIcons.home); + break; + default: + } + } + + @observable + String appBarTitle = AppRoute.Home.title; + + @action + void updateAppBarTitle(String title) => appBarTitle = title; + + @observable + IconData appBarIcon = OMIcons.settings; + + @action + void updateAppBarIcon(IconData icon) => appBarIcon = icon; + + @observable + AppTheme currentTheme = AppTheme.Light; + + @action + void setTheme(AppTheme theme) { + currentTheme = theme; + } + + @observable + ZeroNetStatus zeroNetStatus = ZeroNetStatus.NOT_RUNNING; + + @action + void setZeroNetStatus(ZeroNetStatus status) { + zeroNetStatus = status; + } +} diff --git a/lib/mobx/uistore.g.dart b/lib/mobx/uistore.g.dart new file mode 100644 index 0000000..e2e6dd0 --- /dev/null +++ b/lib/mobx/uistore.g.dart @@ -0,0 +1,256 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'uistore.dart'; + +// ************************************************************************** +// StoreGenerator +// ************************************************************************** + +// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic + +mixin _$UiStore on _UiStore, Store { + final _$appUpdateAtom = Atom(name: '_UiStore.appUpdate'); + + @override + AppUpdate get appUpdate { + _$appUpdateAtom.context.enforceReadPolicy(_$appUpdateAtom); + _$appUpdateAtom.reportObserved(); + return super.appUpdate; + } + + @override + set appUpdate(AppUpdate value) { + _$appUpdateAtom.context.conditionallyRunInAction(() { + super.appUpdate = value; + _$appUpdateAtom.reportChanged(); + }, _$appUpdateAtom, name: '${_$appUpdateAtom.name}_set'); + } + + final _$showSnackReplyAtom = Atom(name: '_UiStore.showSnackReply'); + + @override + bool get showSnackReply { + _$showSnackReplyAtom.context.enforceReadPolicy(_$showSnackReplyAtom); + _$showSnackReplyAtom.reportObserved(); + return super.showSnackReply; + } + + @override + set showSnackReply(bool value) { + _$showSnackReplyAtom.context.conditionallyRunInAction(() { + super.showSnackReply = value; + _$showSnackReplyAtom.reportChanged(); + }, _$showSnackReplyAtom, name: '${_$showSnackReplyAtom.name}_set'); + } + + final _$reloadAtom = Atom(name: '_UiStore.reload'); + + @override + int get reload { + _$reloadAtom.context.enforceReadPolicy(_$reloadAtom); + _$reloadAtom.reportObserved(); + return super.reload; + } + + @override + set reload(int value) { + _$reloadAtom.context.conditionallyRunInAction(() { + super.reload = value; + _$reloadAtom.reportChanged(); + }, _$reloadAtom, name: '${_$reloadAtom.name}_set'); + } + + final _$currentSiteInfoAtom = Atom(name: '_UiStore.currentSiteInfo'); + + @override + SiteInfo get currentSiteInfo { + _$currentSiteInfoAtom.context.enforceReadPolicy(_$currentSiteInfoAtom); + _$currentSiteInfoAtom.reportObserved(); + return super.currentSiteInfo; + } + + @override + set currentSiteInfo(SiteInfo value) { + _$currentSiteInfoAtom.context.conditionallyRunInAction(() { + super.currentSiteInfo = value; + _$currentSiteInfoAtom.reportChanged(); + }, _$currentSiteInfoAtom, name: '${_$currentSiteInfoAtom.name}_set'); + } + + final _$currentAppRouteAtom = Atom(name: '_UiStore.currentAppRoute'); + + @override + AppRoute get currentAppRoute { + _$currentAppRouteAtom.context.enforceReadPolicy(_$currentAppRouteAtom); + _$currentAppRouteAtom.reportObserved(); + return super.currentAppRoute; + } + + @override + set currentAppRoute(dynamic value) { + _$currentAppRouteAtom.context.conditionallyRunInAction(() { + super.currentAppRoute = value; + _$currentAppRouteAtom.reportChanged(); + }, _$currentAppRouteAtom, name: '${_$currentAppRouteAtom.name}_set'); + } + + final _$appBarTitleAtom = Atom(name: '_UiStore.appBarTitle'); + + @override + String get appBarTitle { + _$appBarTitleAtom.context.enforceReadPolicy(_$appBarTitleAtom); + _$appBarTitleAtom.reportObserved(); + return super.appBarTitle; + } + + @override + set appBarTitle(dynamic value) { + _$appBarTitleAtom.context.conditionallyRunInAction(() { + super.appBarTitle = value; + _$appBarTitleAtom.reportChanged(); + }, _$appBarTitleAtom, name: '${_$appBarTitleAtom.name}_set'); + } + + final _$appBarIconAtom = Atom(name: '_UiStore.appBarIcon'); + + @override + IconData get appBarIcon { + _$appBarIconAtom.context.enforceReadPolicy(_$appBarIconAtom); + _$appBarIconAtom.reportObserved(); + return super.appBarIcon; + } + + @override + set appBarIcon(IconData value) { + _$appBarIconAtom.context.conditionallyRunInAction(() { + super.appBarIcon = value; + _$appBarIconAtom.reportChanged(); + }, _$appBarIconAtom, name: '${_$appBarIconAtom.name}_set'); + } + + final _$currentThemeAtom = Atom(name: '_UiStore.currentTheme'); + + @override + AppTheme get currentTheme { + _$currentThemeAtom.context.enforceReadPolicy(_$currentThemeAtom); + _$currentThemeAtom.reportObserved(); + return super.currentTheme; + } + + @override + set currentTheme(AppTheme value) { + _$currentThemeAtom.context.conditionallyRunInAction(() { + super.currentTheme = value; + _$currentThemeAtom.reportChanged(); + }, _$currentThemeAtom, name: '${_$currentThemeAtom.name}_set'); + } + + final _$zeroNetStatusAtom = Atom(name: '_UiStore.zeroNetStatus'); + + @override + ZeroNetStatus get zeroNetStatus { + _$zeroNetStatusAtom.context.enforceReadPolicy(_$zeroNetStatusAtom); + _$zeroNetStatusAtom.reportObserved(); + return super.zeroNetStatus; + } + + @override + set zeroNetStatus(dynamic value) { + _$zeroNetStatusAtom.context.conditionallyRunInAction(() { + super.zeroNetStatus = value; + _$zeroNetStatusAtom.reportChanged(); + }, _$zeroNetStatusAtom, name: '${_$zeroNetStatusAtom.name}_set'); + } + + final _$_UiStoreActionController = ActionController(name: '_UiStore'); + + @override + void updateInAppUpdateAvailable(AppUpdate available) { + final _$actionInfo = _$_UiStoreActionController.startAction(); + try { + return super.updateInAppUpdateAvailable(available); + } finally { + _$_UiStoreActionController.endAction(_$actionInfo); + } + } + + @override + void updateShowSnackReply(bool show) { + final _$actionInfo = _$_UiStoreActionController.startAction(); + try { + return super.updateShowSnackReply(show); + } finally { + _$_UiStoreActionController.endAction(_$actionInfo); + } + } + + @override + void updateReload(int i) { + final _$actionInfo = _$_UiStoreActionController.startAction(); + try { + return super.updateReload(i); + } finally { + _$_UiStoreActionController.endAction(_$actionInfo); + } + } + + @override + void updateCurrentSiteInfo(SiteInfo siteInfo) { + final _$actionInfo = _$_UiStoreActionController.startAction(); + try { + return super.updateCurrentSiteInfo(siteInfo); + } finally { + _$_UiStoreActionController.endAction(_$actionInfo); + } + } + + @override + void updateCurrentAppRoute(dynamic appRoute) { + final _$actionInfo = _$_UiStoreActionController.startAction(); + try { + return super.updateCurrentAppRoute(appRoute); + } finally { + _$_UiStoreActionController.endAction(_$actionInfo); + } + } + + @override + void updateAppBarTitle(dynamic title) { + final _$actionInfo = _$_UiStoreActionController.startAction(); + try { + return super.updateAppBarTitle(title); + } finally { + _$_UiStoreActionController.endAction(_$actionInfo); + } + } + + @override + void updateAppBarIcon(IconData icon) { + final _$actionInfo = _$_UiStoreActionController.startAction(); + try { + return super.updateAppBarIcon(icon); + } finally { + _$_UiStoreActionController.endAction(_$actionInfo); + } + } + + @override + void setTheme(AppTheme theme) { + final _$actionInfo = _$_UiStoreActionController.startAction(); + try { + return super.setTheme(theme); + } finally { + _$_UiStoreActionController.endAction(_$actionInfo); + } + } + + @override + void setZeroNetStatus(dynamic status) { + final _$actionInfo = _$_UiStoreActionController.startAction(); + try { + return super.setZeroNetStatus(status); + } finally { + _$_UiStoreActionController.endAction(_$actionInfo); + } + } +} diff --git a/lib/mobx/varstore.dart b/lib/mobx/varstore.dart index 94a6625..ab8798a 100644 --- a/lib/mobx/varstore.dart +++ b/lib/mobx/varstore.dart @@ -49,13 +49,13 @@ abstract class _VarStore with Store { zeroNetAppbarStatus = status; } - @observable - String zeroNetStatus = 'Not Running'; + // @observable + // String zeroNetStatus = 'Not Running'; - @action - void setZeroNetStatus(String status) { - zeroNetStatus = status; - } + // @action + // void setZeroNetStatus(String status) { + // zeroNetStatus = status; + // } @observable bool zeroNetInstalled = false; diff --git a/lib/mobx/varstore.g.dart b/lib/mobx/varstore.g.dart index e3e39d8..218caf3 100644 --- a/lib/mobx/varstore.g.dart +++ b/lib/mobx/varstore.g.dart @@ -79,23 +79,6 @@ mixin _$VarStore on _VarStore, Store { name: '${_$zeroNetAppbarStatusAtom.name}_set'); } - final _$zeroNetStatusAtom = Atom(name: '_VarStore.zeroNetStatus'); - - @override - String get zeroNetStatus { - _$zeroNetStatusAtom.context.enforceReadPolicy(_$zeroNetStatusAtom); - _$zeroNetStatusAtom.reportObserved(); - return super.zeroNetStatus; - } - - @override - set zeroNetStatus(String value) { - _$zeroNetStatusAtom.context.conditionallyRunInAction(() { - super.zeroNetStatus = value; - _$zeroNetStatusAtom.reportChanged(); - }, _$zeroNetStatusAtom, name: '${_$zeroNetStatusAtom.name}_set'); - } - final _$zeroNetInstalledAtom = Atom(name: '_VarStore.zeroNetInstalled'); @override @@ -206,16 +189,6 @@ mixin _$VarStore on _VarStore, Store { } } - @override - void setZeroNetStatus(String status) { - final _$actionInfo = _$_VarStoreActionController.startAction(); - try { - return super.setZeroNetStatus(status); - } finally { - _$_VarStoreActionController.endAction(_$actionInfo); - } - } - @override void isZeroNetInstalled(bool installed) { final _$actionInfo = _$_VarStoreActionController.startAction(); diff --git a/lib/models/enums.dart b/lib/models/enums.dart new file mode 100644 index 0000000..e68110d --- /dev/null +++ b/lib/models/enums.dart @@ -0,0 +1,192 @@ +import 'package:flutter/material.dart'; +import 'package:in_app_update/in_app_update.dart'; +import 'package:zeronet/mobx/uistore.dart'; +import 'package:zeronet/others/zeronet_utils.dart'; + +enum ZeroNetStatus { + NOT_RUNNING, + INITIALISING, + RUNNING, + RUNNING_WITH_TOR, + ERROR, +} + +extension ZeroNetStatusExt on ZeroNetStatus { + get message { + switch (this) { + case ZeroNetStatus.NOT_RUNNING: + return 'Not Running'; + break; + case ZeroNetStatus.INITIALISING: + return 'Initialising...'; + break; + case ZeroNetStatus.RUNNING: + return 'Running'; + break; + case ZeroNetStatus.RUNNING_WITH_TOR: + return 'Running with Tor'; + break; + case ZeroNetStatus.ERROR: + return 'Error'; + break; + default: + } + } + + get actionText { + switch (this) { + case ZeroNetStatus.NOT_RUNNING: + return 'Start'; + break; + case ZeroNetStatus.INITIALISING: + return 'Please WAIT!'; + break; + case ZeroNetStatus.RUNNING: + case ZeroNetStatus.RUNNING_WITH_TOR: + return 'Stop'; + break; + case ZeroNetStatus.ERROR: + return 'View Log'; + break; + default: + } + } + + void onAction() { + switch (this) { + case ZeroNetStatus.NOT_RUNNING: + runZeroNet(); + break; + case ZeroNetStatus.RUNNING: + case ZeroNetStatus.RUNNING_WITH_TOR: + shutDownZeronet(); + break; + case ZeroNetStatus.ERROR: + uiStore.updateCurrentAppRoute(AppRoute.LogPage); + break; + default: + return null; + } + } + + get actionBtnColor { + switch (this) { + case ZeroNetStatus.NOT_RUNNING: + return Color(0xFF52F7C5); + break; + case ZeroNetStatus.INITIALISING: + return Color(0xFF5A53FF); + break; + case ZeroNetStatus.RUNNING: + case ZeroNetStatus.RUNNING_WITH_TOR: + return Color(0xFFF6595F); + break; + case ZeroNetStatus.ERROR: + return Color(0xFF5A53FF); + break; + default: + } + } + + get statusChipColor { + switch (this) { + case ZeroNetStatus.NOT_RUNNING: + return Color(0xFFF6595F); + break; + case ZeroNetStatus.INITIALISING: + return Color(0xFF5A53FF); + break; + case ZeroNetStatus.RUNNING: + case ZeroNetStatus.RUNNING_WITH_TOR: + return Color(0xFF52F7C5); + break; + case ZeroNetStatus.ERROR: + return Color(0xFFF6595F); + break; + default: + } + } +} + +enum AppUpdate { + NOT_AVAILABLE, + AVAILABLE, + DOWNLOADING, + DOWNLOADED, + INSTALLING, +} + +extension AppUpdateExt on AppUpdate { + get text { + switch (uiStore.appUpdate) { + case AppUpdate.AVAILABLE: + return 'Update'; + break; + case AppUpdate.DOWNLOADING: + return 'Downloading'; + break; + case AppUpdate.DOWNLOADED: + return 'Downloaded'; + break; + case AppUpdate.INSTALLING: + return 'Installing'; + break; + default: + return 'Not Available'; + } + } + + void action() { + switch (uiStore.appUpdate) { + case AppUpdate.AVAILABLE: + { + InAppUpdate.startFlexibleUpdate().then((value) => + uiStore.updateInAppUpdateAvailable(AppUpdate.DOWNLOADED)); + uiStore.updateInAppUpdateAvailable(AppUpdate.DOWNLOADING); + } + break; + case AppUpdate.DOWNLOADED: + { + InAppUpdate.completeFlexibleUpdate().then((value) => + uiStore.updateInAppUpdateAvailable(AppUpdate.NOT_AVAILABLE)); + uiStore.updateInAppUpdateAvailable(AppUpdate.INSTALLING); + } + break; + default: + } + } +} + +enum AppRoute { + Home, + Settings, + ShortcutLoadingPage, + ZeroBrowser, + LogPage, +} + +extension AppRouteExt on AppRoute { + get title { + switch (this) { + case AppRoute.Home: + return 'ZeroNet Mobile'; + break; + case AppRoute.Settings: + return 'Settings'; + break; + case AppRoute.ZeroBrowser: + return 'ZeroBrowser'; + break; + case AppRoute.LogPage: + return 'ZeroNet Log'; + break; + default: + } + } +} + +enum AppTheme { + Light, + Dark, + Black, +} diff --git a/lib/models/models.dart b/lib/models/models.dart index bf3c211..4a64f5d 100644 --- a/lib/models/models.dart +++ b/lib/models/models.dart @@ -1,6 +1,9 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:equatable/equatable.dart'; +import 'package:zeronet/core/site/site.dart'; + import '../mobx/varstore.dart'; import '../others/common.dart'; import '../others/constants.dart'; @@ -81,11 +84,13 @@ class MapSetting extends Setting { String name; String description; Map map; + List options; MapSetting({ this.name, this.description, this.map, + this.options, }) : super( name: name, description: description, @@ -110,49 +115,6 @@ class MapSetting extends Setting { } } -class Utils { - static const String urlHello = '1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D'; - static const String urlTalk = 'Talk.ZeroNetwork.bit'; - static const String urlBlog = 'Blog.ZeroNetwork.bit'; - static const String urlMail = 'Mail.ZeroNetwork.bit'; - static const String urlMe = 'Me.ZeroNetwork.bit'; - static const String urlSites = 'Sites.ZeroNetwork.bit'; - static const String urlZeroNetMob = '15UYrA7aXr2Nto1Gg4yWXpY3EAJwafMTNk'; - - static const initialSites = const { - 'ZeroHello': { - 'description': 'Hello Zeronet Site', - 'url': urlHello, - }, - 'ZeroMobile': { - 'description': 'Report Android App Issues Here.', - 'url': urlZeroNetMob, - }, - 'ZeroTalk': { - 'description': 'Reddit-like, decentralized forum', - 'url': urlTalk, - }, - 'ZeroBlog': { - 'description': 'Microblogging Platform', - 'url': urlBlog, - }, - 'ZeroMail': { - 'description': 'End-to-End Encrypted Mailing', - 'url': urlMail, - }, - 'ZeroMe': { - 'description': 'P2P Social Network', - 'url': urlMe, - }, - 'ZeroSites': { - 'description': 'Discover More Sites', - 'url': urlSites, - }, - }; - - // 'ZeroName': '1Name2NXVi1RDPDgf5617UoW7xA6YrhM9F', -} - class UnzipParams { String item; Uint8List bytes; @@ -174,3 +136,77 @@ enum state { RUNNING, NONE, } + +class SiteInfo extends Equatable { + final String address; + final int peers; + final int size; + final int files; + final DateTime siteAdded; + final DateTime siteModified; + final bool serving; + final DateTime siteCodeUpdated; + SiteInfo({ + this.address = '', + this.peers = 0, + this.size = 0, + this.files = 0, + this.serving = false, + this.siteAdded, + this.siteModified, + this.siteCodeUpdated, + }); + + @override + List get props => [ + address, + peers, + serving, + size, + files, + siteAdded, + siteModified, + siteCodeUpdated, + ]; + + List get propStrings => [ + 'address', + 'peers', + 'serving', + 'size', + 'files', + 'siteAdded', + 'siteModified', + 'siteCodeUpdated', + ]; + + SiteInfo fromJson(String jsonMap) { + Map map = json.decode(jsonMap)['result']; + return SiteInfo( + address: map['address'], + peers: map['peers'], + serving: map['settings']['serving'], + size: map['settings']['size'], + files: map['content']['files'], + siteAdded: + DateTime.fromMillisecondsSinceEpoch(map['settings']['added'] * 1000), + siteModified: DateTime.fromMillisecondsSinceEpoch( + map['settings']['downloaded'] * 1000), + siteCodeUpdated: DateTime.fromMillisecondsSinceEpoch( + map['settings']['modified'] * 1000), + ); + } + + SiteInfo fromSite(Site site) { + return SiteInfo( + address: site.address, + peers: site.peers, + serving: site.serving, + size: site.size, + siteAdded: DateTime.fromMillisecondsSinceEpoch(site.added * 1000), + siteModified: DateTime.fromMillisecondsSinceEpoch(site.downloaded * 1000), + siteCodeUpdated: + DateTime.fromMillisecondsSinceEpoch(site.modified * 1000), + ); + } +} diff --git a/lib/others/common.dart b/lib/others/common.dart index 37a3109..89f8f75 100644 --- a/lib/others/common.dart +++ b/lib/others/common.dart @@ -10,6 +10,10 @@ import 'package:http/http.dart'; import 'package:package_info/package_info.dart'; import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:zeronet/core/site/site.dart'; +import 'package:zeronet/core/user/user.dart'; +import 'package:zeronet/mobx/uistore.dart'; +import 'package:zeronet/models/enums.dart'; import '../mobx/varstore.dart'; import '../models/models.dart'; @@ -37,11 +41,17 @@ var zeroNetState = state.NONE; Client client = Client(); String arch; String zeroNetUrl = ''; +String launchUrl = ''; String zeroNetNativeDir = ''; String zeroNetIPwithPort(String url) => url.replaceAll('http:', '').replaceAll('/', '').replaceAll('s', ''); String sesionKey = ''; String browserUrl = 'https://google.com'; +Map sitesAvailable = {}; +List usersAvailable = []; +String zeroBrowserTheme = 'light'; +// Color zeroBrowserPrimaryColor; +// Color zeroBrowserAccentColor; FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; @@ -251,10 +261,11 @@ printToConsole(Object object) { testUrl(); } } - if (object.contains('Server port opened') || + if (object.contains('Ui.UiServer Web interface: ') || + object.contains('Server port opened') || object.contains(zeronetAlreadyRunningError)) { runZeroNetWs(); - varStore.setZeroNetStatus('Running'); + uiStore.setZeroNetStatus(ZeroNetStatus.RUNNING); bool vibrate = (varStore.settings[vibrateOnZeroNetStart] as ToggleSetting).value; showZeroNetRunningNotification(enableVibration: vibrate); @@ -262,14 +273,14 @@ printToConsole(Object object) { if (object.contains('ConnServer Closed port') || object.contains('All server stopped')) { zeroNetUrl = ''; - varStore.setZeroNetStatus('Not Running'); + uiStore.setZeroNetStatus(ZeroNetStatus.NOT_RUNNING); flutterLocalNotificationsPlugin.cancelAll(); } log = log + object + '\n'; } else { if (object.contains(zeronetAlreadyRunningError)) { runZeroNetWs(); - varStore.setZeroNetStatus('Running'); + uiStore.setZeroNetStatus(ZeroNetStatus.RUNNING); bool vibrate = (varStore.settings[vibrateOnZeroNetStart] as ToggleSetting).value; showZeroNetRunningNotification(enableVibration: vibrate); diff --git a/lib/others/constants.dart b/lib/others/constants.dart index 2ed4800..22104bf 100644 --- a/lib/others/constants.dart +++ b/lib/others/constants.dart @@ -1,5 +1,17 @@ +import 'dart:io'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:zeronet/mobx/uistore.dart'; +import 'package:zeronet/models/enums.dart'; +import 'package:zeronet/models/models.dart'; +import 'package:zeronet/others/utils.dart'; +import 'package:zeronet/others/zeronet_utils.dart'; +import 'package:zeronet/widgets/common.dart'; +import 'package:zeronet_ws/zeronet_ws.dart'; + +import 'common.dart'; +import 'native.dart'; const String pkgName = 'in.canews.zeronet${kDebugMode ? '.debug' : ''}'; const String dataDir = "/data/data/$pkgName/files"; @@ -30,19 +42,9 @@ const String zeronetAlreadyRunningError = zeronetStartUpError + 'Can\'t open lock file'; const bool kEnableDynamicModules = !kDebugMode; -const List colors = [ - Colors.cyan, - Colors.indigoAccent, - Color(0xFF9F63BF), - Color(0xFFE2556F), - Color(0xFF358AC7), - Color(0xFFA176B6), - Color(0xFFC04848), - Color(0xFF4A569D), - Color(0xFFF39963), - Color(0xFFF1417D), - Color(0xFF1BB2D4), - Color(0xFF7ECA26), +const List unImplementedFeatures = [ + Feature.SITE_DELETE, + Feature.SITE_PAUSE_RESUME, ]; const List binDirs = [ 'usr', @@ -84,3 +86,332 @@ const String autoStartZeroNetDes = const String autoStartZeroNetonBoot = 'AutoStart ZeroNet on Boot'; const String autoStartZeroNetonBootDes = 'This Will Make ZeroNet Auto Start on Device Boot.'; + +class Utils { + static const String urlHello = '1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D'; + static const String urlZeroNetMob = '15UYrA7aXr2Nto1Gg4yWXpY3EAJwafMTNk'; + static const String urlTalk = 'Talk.ZeroNetwork.bit'; + static const String btcUrlTalk = '1TaLkFrMwvbNsooF4ioKAY9EuxTBTjipT'; + static const String urlBlog = 'Blog.ZeroNetwork.bit'; + static const String btcUrlBlog = '1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8'; + static const String urlMail = 'Mail.ZeroNetwork.bit'; + static const String btcUrlMail = '1MaiL5gfBM1cyb4a8e3iiL8L5gXmoAJu27'; + static const String urlMe = 'Me.ZeroNetwork.bit'; + static const String btcUrlMe = '1MeFqFfFFGQfa1J3gJyYYUvb5Lksczq7nH'; + static const String urlSites = 'Sites.ZeroNetwork.bit'; + static const String btcUrlSites = '1SiTEs2D3rCBxeMoLHXei2UYqFcxctdwB'; + + static const initialSites = const { + 'ZeroHello': { + 'description': 'Say Hello to ZeroNet, a Dashboard to manage ' + + 'all your ZeroNet Z(S)ites, You can view feed of other zites like ' + + 'posts, comments of other users from ZeroTalk as well for your posts ' + + 'and Stats like Total Requests sent and received from other peers on ZeroNet. ' + + 'You can also pause, clone or favourite, delete Zites from single page.', + 'url': urlHello, + 'btcAddress': urlHello, + }, + 'ZeroNetMobile': { + 'description': 'Forum to report ZeroNet Mobile app issues. ' + + 'Want a new feature in the app, Request a Feature. ' + + 'Facing any Bugs while using the app ? ' + + 'Just report problem here, we will take care of it. ' + + 'Want to Discuss any topic about app development ? ' + + 'Just dive into to this Zite.', + 'url': urlZeroNetMob, + 'btcAddress': urlZeroNetMob, + }, + 'ZeroTalk': { + 'description': 'Need a forum to discuss something, ' + + 'we got covered you here. ZeroTalk fits your need, ' + + 'just post something to get opinion from others on Network. ' + + 'Have some queries ? don\'t hesitate to ask here.' + + 'Tired of Spam ? Who don\'t! You can mute individual users also.', + 'url': urlTalk, + 'btcAddress': btcUrlTalk, + }, + 'ZeroBlog': { + 'description': 'Want to Know Where ZeroNet is Going ? ' + + 'ZeroBlog gives you latest changes and improvements ' + + 'made to ZeroNet, including Bug Fixes, ' + + 'Speed Improvements of ZeroNet Core Software. ' + + 'Also Provides varies links to ZeroNet Protocol and ' + + 'how ZeroNet works underhood and much more things to know.', + 'url': urlBlog, + 'btcAddress': btcUrlBlog, + }, + 'ZeroMail': { + 'description': 'So you need a mail service, use ZeroMail, ' + + 'fully end-to-end encrypted mail service on ZeroNet, ' + + 'don\'t let others scanning your mailbox for their profits ' + + 'all your data is encrypted and can only opened by you. ' + + 'Your all mails are backedup, so you can stay calm for your data.', + 'url': urlMail, + 'btcAddress': btcUrlMail, + }, + 'ZeroMe': { + 'description': 'Social Network is everywhere, so we made one here too. ' + + 'Twitter like, Peer to Peer Social Networking in your hands without data-tracking, ' + + 'Follow others and post your thoughts, like, comment on others posts, it\'s that easy-peasy. ' + + 'Find Like minded people and increase your friend circle beyond the borders.', + 'url': urlMe, + 'btcAddress': btcUrlMe, + }, + 'ZeroSites': { + 'description': 'Want to know more sites on ZeroNet, ' + + 'visit ZeroSites, listing of community contributed sites under various ' + + 'categories like Blogs, Services, Forums, Chat, Video, Image, Guides, News and much more. ' + + 'You can even filter those lists with your preferred language ' + + 'to get more comprehensive list. Has a New Site to Show, Just Submit here.', + 'url': urlSites, + 'btcAddress': btcUrlSites, + }, + }; + + // 'ZeroName': '1Name2NXVi1RDPDgf5617UoW7xA6YrhM9F', + + static const String createProfile = 'Create'; // New Profile + static const String importProfile = 'Import'; // Profile + static const String backupProfile = 'Backup'; // Profile + + static const String openPluginManager = 'Open Plugin Manager'; + static const String loadPlugin = 'Load Custom Plugin'; + + Map defSettings = { + profileSwitcher: MapSetting( + name: profileSwitcher, + description: profileSwitcherDes, + map: { + "selected": '', + "all": [], + }, + options: [ + MapOptions.CREATE_PROFILE, + MapOptions.IMPORT_PROFILE, + MapOptions.BACKUP_PROFILE, + ]), + pluginManager: MapSetting( + name: pluginManager, + description: pluginManagerDes, + map: {}, + options: [ + MapOptions.OPEN_PLUGIN_MANAGER, + MapOptions.LOAD_PLUGIN, + ]), + batteryOptimisation: ToggleSetting( + name: batteryOptimisation, + description: batteryOptimisationDes, + value: false, + ), + publicDataFolder: ToggleSetting( + name: publicDataFolder, + description: publicDataFolderDes, + value: false, + ), + vibrateOnZeroNetStart: ToggleSetting( + name: vibrateOnZeroNetStart, + description: vibrateOnZeroNetStartDes, + value: false, + ), + enableFullScreenOnWebView: ToggleSetting( + name: enableFullScreenOnWebView, + description: enableFullScreenOnWebViewDes, + value: false, + ), + autoStartZeroNet: ToggleSetting( + name: autoStartZeroNet, + description: autoStartZeroNetDes, + value: true, + ), + autoStartZeroNetonBoot: ToggleSetting( + name: autoStartZeroNetonBoot, + description: autoStartZeroNetonBootDes, + value: false, + ), + debugZeroNet: ToggleSetting( + name: debugZeroNet, + description: debugZeroNetDes, + value: false, + ), + enableZeroNetConsole: ToggleSetting( + name: enableZeroNetConsole, + description: enableZeroNetConsoleDes, + value: false, + ), + }; +} + +enum MapOptions { + CREATE_PROFILE, + IMPORT_PROFILE, + BACKUP_PROFILE, + + OPEN_PLUGIN_MANAGER, + LOAD_PLUGIN, +} + +extension MapOptionExt on MapOptions { + get description { + switch (this) { + case MapOptions.CREATE_PROFILE: + return 'Create New Profile'; + break; + case MapOptions.IMPORT_PROFILE: + return 'Import Profile'; + break; + case MapOptions.BACKUP_PROFILE: + return 'Backup Profile'; + break; + case MapOptions.OPEN_PLUGIN_MANAGER: + return 'Open Plugin Manager'; + break; + case MapOptions.LOAD_PLUGIN: + return 'Load Plugin'; + break; + } + } + + void onClick(BuildContext context) async { + switch (this) { + case MapOptions.CREATE_PROFILE: + { + if (isZeroNetUserDataExists()) { + showDialogW( + context: context, + title: 'Provide A Name for Existing Profile', + body: ProfileSwitcherUserNameEditText(), + actionOk: Row( + children: [ + FlatButton( + child: Text('Create'), + onPressed: () { + if (username.isNotEmpty) { + File file = File(getZeroNetUsersFilePath()); + var f = file.renameSync( + getZeroNetDataDir().path + '/users-$username.json'); + if (f.existsSync()) { + if (file.existsSync()) file.deleteSync(); + Navigator.pop(context); + ZeroNet.instance.shutDown(); + runZeroNet(); + } + username = ''; + uiStore.updateCurrentAppRoute(AppRoute.Settings); + uiStore.updateReload(uiStore.reload++); + } else { + validUsername = false; + // _reload(); + } + }, + ), + FlatButton( + child: Text('Backup'), + onPressed: () => backUpUserJsonFile(context), + ), + ], + ), + ); + } else + zeronetNotInit(context); + } + break; + case MapOptions.IMPORT_PROFILE: + { + var file = await getUserJsonFile(); + if (file != null && file.path.endsWith('users.json')) { + var isSameUser = file.existsSync() + ? getZeroNetUsersFilePath() == file.path + : false; + showDialogW( + context: context, + title: 'Restore Profile ?', + body: Text( + 'this will delete the existing profile, ' + 'backup existing profile using backup button below\n\n' + 'Selected Userfile : \n' + '$filePath' + '\n\n${isSameUser ? 'You can only select users.json file, outside zeronet data folder' : ''}', + ), + actionOk: Row( + children: [ + FlatButton( + onPressed: isSameUser + ? null + : () async { + File f = File(getZeroNetUsersFilePath()); + printOut(f.path); + if (!isSameUser) { + if (f.existsSync()) f.deleteSync(); + f.createSync(); + f.writeAsStringSync(file.readAsStringSync()); + // _reload(); + try { + ZeroNet.instance.shutDown(); + } catch (e) { + printOut(e); + } + runZeroNet(); + Navigator.pop(context); + } + }, + child: Text( + 'Restore', + ), + ), + FlatButton( + child: Text('Backup'), + onPressed: () => backUpUserJsonFile(context), + ), + ], + ), + ); + } + } + break; + case MapOptions.BACKUP_PROFILE: + backUpUserJsonFile(context); + break; + case MapOptions.LOAD_PLUGIN: + { + showDialogW( + context: context, + title: 'Install a Plugin', + body: Text('This will load plugin to your ZeroNet repo, ' + '\nWarning : Loading Unknown/Untrusted plugins may compromise ZeroNet Installation.'), + actionOk: FlatButton( + onPressed: () async { + var file = await getPluginZipFile(); + if (file != null) { + Navigator.pop(context); + installPluginDialog(file, context); + } + }, + child: Text('Install'), + ), + ); + } + break; + case MapOptions.OPEN_PLUGIN_MANAGER: + showDialogW( + context: context, + title: pluginManager, + body: PluginManager(), + actionOk: FlatButton( + onPressed: () { + ZeroNet.instance.shutDown(); + runZeroNet(); + Navigator.pop(context); + }, + child: Text('Restart'), + ), + ); + break; + default: + } + } +} + +enum Feature { + SITE_PAUSE_RESUME, + SITE_DELETE, +} diff --git a/lib/others/extensions.dart b/lib/others/extensions.dart index 1c93650..4999ad6 100644 --- a/lib/others/extensions.dart +++ b/lib/others/extensions.dart @@ -3,3 +3,20 @@ import 'dart:io'; extension FileSystemExtension on FileSystemEntity { String name() => this.path.replaceFirst(this.parent.path + '/', ''); } + +extension CapExtension on String { + String get inCaps => '${this[0].toUpperCase()}${this.substring(1)}'; + String get allInCaps => this.toUpperCase(); + String get capitalizeFirstofEach => + this.split(" ").map((str) => str.inCaps).join(" "); +} + +extension DynamicExt on dynamic { + int toInt() { + if (this is num) { + if (this is double) return this.toInt(); + if (this is int) return this; + } + return -1; + } +} diff --git a/lib/others/native.dart b/lib/others/native.dart index 41701f6..f6b55b7 100644 --- a/lib/others/native.dart +++ b/lib/others/native.dart @@ -15,6 +15,16 @@ const MethodChannel _channel = const MethodChannel('in.canews.zeronet'); const EventChannel _events_channel = const EventChannel('in.canews.zeronet/installModules'); +Future addToHomeScreen(String title, String url, String logoPath) async => + await _channel.invokeMethod('addToHomeScreen', { + 'title': title, + 'url': url, + 'logoPath': logoPath, + }); + +Future launchZiteUrl() async => + await _channel.invokeMethod('launchZiteUrl'); + Future askBatteryOptimisation() async => await _channel.invokeMethod('batteryOptimisations'); @@ -39,6 +49,12 @@ Future isRequiredModulesInstalled() async => Future copyAssetsToCache() async => await _channel.invokeMethod('copyAssetsToCache'); +Future getAppInstallTime() async => + await _channel.invokeMethod('getAppInstallTime'); + +Future getAppLastUpdateTime() async => + await _channel.invokeMethod('getAppLastUpdateTime'); + Future initSplitInstall() async => await _channel.invokeMethod('initSplitInstall'); @@ -67,15 +83,22 @@ void handleModuleDownloadStatus() { String filePath = ''; Future getUserJsonFile() async { String uri; - if (deviceInfo.version.sdkInt > 28) { - uri = await _channel.invokeMethod('openJsonFile'); - filePath = await FlutterAbsolutePath.getAbsolutePath(uri); - } else { - uri = (await pickUserJsonFile()).path; - filePath = uri; + try { + if (deviceInfo.version.sdkInt > 28) { + uri = await _channel.invokeMethod('openJsonFile'); + filePath = await FlutterAbsolutePath.getAbsolutePath(uri); + } else { + uri = (await pickUserJsonFile()).path; + filePath = uri; + } + String path = await _channel.invokeMethod('readJsonFromUri', uri); + return File(path); + } catch (e) { + if (e is PlatformException && e.code == '526') { + return null; + } + return null; } - String path = await _channel.invokeMethod('readJsonFromUri', uri); - return File(path); } Future getPluginZipFile() async { diff --git a/lib/others/zeronet_utils.dart b/lib/others/zeronet_utils.dart index f42ae76..00842cc 100644 --- a/lib/others/zeronet_utils.dart +++ b/lib/others/zeronet_utils.dart @@ -1,6 +1,12 @@ import 'dart:convert'; import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:in_app_update/in_app_update.dart'; +import 'package:zeronet/core/site/site_manager.dart'; +import 'package:zeronet/core/user/user_manager.dart'; +import 'package:zeronet/mobx/uistore.dart'; +import 'package:zeronet/models/enums.dart'; import 'package:zeronet_ws/zeronet_ws.dart'; import '../mobx/varstore.dart'; @@ -9,6 +15,69 @@ import '../others/utils.dart'; import 'common.dart'; import 'constants.dart'; import 'extensions.dart'; +import 'native.dart'; + +Future checkInitStatus() async { + loadSitesFromFileSystem(); + loadUsersFromFileSystem(); + setZeroBrowserThemeValues(); + checkForAppUpdates(); + try { + String url = defZeroNetUrl + Utils.initialSites['ZeroNetMobile']['url']; + String key = await ZeroNet.instance.getWrapperKey(url); + zeroNetUrl = defZeroNetUrl; + varStore.zeroNetWrapperKey = key; + uiStore.setZeroNetStatus(ZeroNetStatus.RUNNING); + ZeroNet.instance.connect( + zeroNetIPwithPort(defZeroNetUrl), + Utils.urlZeroNetMob, + ); + showZeroNetRunningNotification(enableVibration: false); + testUrl(); + } catch (e) { + if (launchUrl.isNotEmpty || + !firstTime && + (varStore.settings[autoStartZeroNet] as ToggleSetting).value) { + //TODO: Remember this! + runZeroNet(); + } + if (e is OSError) { + if (e.errorCode == 111) { + printToConsole('Zeronet Not Running'); + uiStore.setZeroNetStatus(ZeroNetStatus.NOT_RUNNING); + } + } + } +} + +checkForAppUpdates() async { + DateTime time = DateTime.now(); + var updateTimeEpoch = int.parse(await getAppLastUpdateTime()); + var updateTime = DateTime.fromMillisecondsSinceEpoch(updateTimeEpoch); + //TODO: Update this checking to days instead of seconds after testing completed; + if (time.difference(updateTime).inSeconds > 3 && !kDebugMode) { + AppUpdateInfo info = await InAppUpdate.checkForUpdate(); + if (info.updateAvailable && info.flexibleUpdateAllowed) + uiStore.updateInAppUpdateAvailable(AppUpdate.AVAILABLE); + } +} + +loadSitesFromFileSystem() { + File sitesFile = File(getZeroNetDataDir().path + '/sites.json'); + if (sitesFile.existsSync()) + sitesAvailable = SiteManager().loadSitesFromFile(sitesFile); +} + +loadUsersFromFileSystem() { + File usersFile = File(getZeroNetDataDir().path + '/users.json'); + if (usersFile.existsSync()) + usersAvailable = UserManager().loadUsersFromFile(usersFile); +} + +setZeroBrowserThemeValues() { + if (usersAvailable.length > 0) + zeroBrowserTheme = usersAvailable.first.settings.theme; +} runTorEngine() { final tor = zeroNetNativeDir + '/libtor.so'; @@ -29,14 +98,16 @@ runTorEngine() { printOut(e.toString()); } }); - } else + } else { //TODO: Improve Error Trace here printToConsole('Tor Binary Not Found'); + uiStore.setZeroNetStatus(ZeroNetStatus.RUNNING); + } } runZeroNet() { - if (varStore.zeroNetStatus == 'Not Running') { - varStore.setZeroNetStatus('Initialising...'); + if (uiStore.zeroNetStatus == ZeroNetStatus.NOT_RUNNING) { + uiStore.setZeroNetStatus(ZeroNetStatus.INITIALISING); runTorEngine(); log = ''; printToConsole(logRunning); @@ -69,11 +140,12 @@ runZeroNet() { if (e is ProcessException) { printOut(e.toString()); } - varStore.setZeroNetStatus('Not Running'); + uiStore.setZeroNetStatus(ZeroNetStatus.ERROR); }); } else { //TODO: Improve Error Trace here printToConsole('Python Binary Not Found'); + uiStore.setZeroNetStatus(ZeroNetStatus.ERROR); var contents = Directory(zeroNetNativeDir).listSync(recursive: true); for (var item in contents) { printToConsole(item.name()); @@ -86,7 +158,7 @@ runZeroNet() { } shutDownZeronet() { - if (varStore.zeroNetStatus == 'Running') { + if (uiStore.zeroNetStatus == ZeroNetStatus.RUNNING) { if (ZeroNet.isInitialised) ZeroNet.instance.shutDown(); else { @@ -98,7 +170,7 @@ shutDownZeronet() { } } zeroNetUrl = ''; - varStore.setZeroNetStatus('Not Running'); + uiStore.setZeroNetStatus(ZeroNetStatus.NOT_RUNNING); flutterLocalNotificationsPlugin.cancelAll(); } } @@ -111,13 +183,13 @@ runZeroNetWs() { .getWrapperKey(zeroNetUrl + Utils.initialSites['ZeroHello']['url']) .then((value) { if (value != null) { - ZeroNet.wrapperKey = value; + // ZeroNet.wrapperKey = value; varStore.zeroNetWrapperKey = value; browserUrl = zeroNetUrl; } }); } else { - ZeroNet.wrapperKey = varStore.zeroNetWrapperKey; + // ZeroNet.wrapperKey = varStore.zeroNetWrapperKey; browserUrl = zeroNetUrl; } } @@ -167,17 +239,12 @@ Directory getZeroNetDataDir() => Directory( List getZeroNameProfiles() { List list = []; if (getZeroNetDataDir().existsSync()) - for (var item in getZeroNetDataDir().listSync()) { - if (item is File) { - if (item.path.endsWith('.json')) { - var name = item.path.replaceAll(getZeroNetDataDir().path + '/', ''); - if (name.startsWith('users-')) { - var username = - name.replaceAll('users-', '').replaceAll('.json', ''); - list.add(username); - printOut(username); - } - } + for (var item in getZeroNetDataDir().listSync().where( + (element) => element.path.endsWith('.json') && element is File)) { + var name = item.path.replaceAll(getZeroNetDataDir().path + '/', ''); + if (name.startsWith('users-')) { + var username = name.replaceAll('users-', '').replaceAll('.json', ''); + list.add(username); } } return list; @@ -185,6 +252,7 @@ List getZeroNameProfiles() { String getZeroIdUserName() { File file = File(getZeroNetUsersFilePath()); + if (!file.existsSync()) return ''; Map map = json.decode(file.readAsStringSync()); var key = map.keys.first; Map certMap = map[key]['certs']; @@ -202,3 +270,8 @@ String getZeroIdUserName() { } return ''; } + +bool isZiteExitsLocally(String address) { + String path = getZeroNetDataDir().path + '/$address'; + return Directory(path).existsSync(); +} diff --git a/lib/widgets/common.dart b/lib/widgets/common.dart new file mode 100644 index 0000000..8e8c428 --- /dev/null +++ b/lib/widgets/common.dart @@ -0,0 +1,194 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:zeronet/mobx/uistore.dart'; +import 'package:zeronet/models/enums.dart'; +import 'package:zeronet/others/constants.dart'; +import 'package:zeronet/others/utils.dart'; +import 'package:zeronet/others/zeronet_utils.dart'; + +class ZeroNetAppBar extends StatelessWidget { + const ZeroNetAppBar({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Observer(builder: (context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + uiStore.appBarTitle, + style: GoogleFonts.roboto( + fontSize: 32.0, + fontWeight: FontWeight.bold, + ), + ), + InkWell( + child: Icon( + uiStore.appBarIcon, + size: 32.0, + color: Colors.black, + ), + onTap: () { + switch (uiStore.currentAppRoute) { + case AppRoute.Home: + uiStore.updateCurrentAppRoute(AppRoute.Settings); + break; + case AppRoute.Settings: + case AppRoute.ZeroBrowser: + case AppRoute.LogPage: + uiStore.updateCurrentAppRoute(AppRoute.Home); + break; + default: + } + }, + ) + ], + ); + }); + } +} + +class PluginManager extends StatefulWidget { + const PluginManager({ + Key key, + }) : super(key: key); + + @override + _PluginManagerState createState() => _PluginManagerState(); +} + +class _PluginManagerState extends State { + _reload() => this.mounted ? setState(() {}) : null; + + @override + Widget build(BuildContext context) { + var size = MediaQuery.of(context).size; + List plugins = []; + var pluginsPath = zeroNetDir + '/plugins/'; + Directory(pluginsPath).listSync().forEach((entity) { + var pycacheDir = entity.path.endsWith('__pycache__'); + if (entity is Directory && !pycacheDir) { + printOut(entity.path); + plugins.insert(0, entity.path.replaceAll(pluginsPath, '')); + } + }); + plugins.sort(); + return Container( + height: size.height * 0.70, + width: size.width, + child: ListView.builder( + itemCount: plugins.length, + itemBuilder: (ctx, i) { + final isDisabled = plugins[i].startsWith('disabled-'); + final pluginName = isDisabled + ? plugins[i].replaceFirst('disabled-', '') + : plugins[i]; + return Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(pluginName), + Switch( + onChanged: (value) { + if (isDisabled) + Directory(pluginsPath + plugins[i]) + .renameSync(pluginsPath + pluginName); + else + Directory(pluginsPath + plugins[i]) + .renameSync(pluginsPath + 'disabled-' + plugins[i]); + _reload(); + }, + value: !isDisabled, + ) + ], + ); + }, + ), + ); + } +} + +var username = ''; +var errorText = ''; +var validUsername = false; + +class ProfileSwitcherUserNameEditText extends StatefulWidget { + @override + _ProfileSwitcherUserNameEditTextState createState() => + _ProfileSwitcherUserNameEditTextState(); +} + +class _ProfileSwitcherUserNameEditTextState + extends State { + TextEditingController _controller = TextEditingController(); + + @override + void initState() { + final usrName = getZeroIdUserName(); + _controller.text = usrName; + if (usrName.isNotEmpty) { + username = usrName; + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ListBody( + children: [ + Text( + 'Always remember to backup users.json before doing anything because, ' + 'we are not able to tell when a software will fail. ' + 'Click Backup below to backup your Existing users.json file.\n', + style: TextStyle( + color: Colors.red, + ), + ), + Text('Username Phrase :'), + Padding( + padding: const EdgeInsets.only( + left: 8.0, + ), + child: TextField( + controller: _controller, + onChanged: (text) { + username = text; + var valid = text.isNotEmpty; + if (valid) { + if (text.contains(' ')) { + errorText = 'username can\'t contain spaces'; + valid = false; + } else if (text.length < 6) { + errorText = 'username can\'t be less than 6 characters.'; + valid = false; + } else if (File(getZeroNetDataDir().path + '/users-$text.json') + .existsSync()) { + errorText = 'username already exists, choose different one.'; + valid = false; + } + } else { + errorText = 'username can\'t be Empty'; + } + setState(() { + validUsername = valid; + }); + }, + style: TextStyle( + fontSize: 18.0, + ), + decoration: InputDecoration( + hintText: 'username', + errorText: validUsername ? null : errorText, + ), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart new file mode 100644 index 0000000..475cc43 --- /dev/null +++ b/lib/widgets/home_page.dart @@ -0,0 +1,682 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:share/share.dart'; +import 'package:time_ago_provider/time_ago_provider.dart' as timeAgo; +import 'package:in_app_review/in_app_review.dart'; + +import 'package:zeronet/core/site/site.dart'; +import 'package:zeronet/core/site/site_manager.dart'; +import 'package:zeronet/mobx/uistore.dart'; +import 'package:zeronet/models/enums.dart'; +import 'package:zeronet/models/models.dart'; +import 'package:zeronet/others/common.dart'; +import 'package:zeronet/others/constants.dart'; +import 'package:zeronet/others/extensions.dart'; +import 'package:zeronet/others/native.dart'; +import 'package:zeronet/others/zeronet_utils.dart'; + +import 'common.dart'; + +class HomePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + height: MediaQuery.of(context).size.height, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Padding(padding: EdgeInsets.all(24)), + Padding( + padding: const EdgeInsets.only(left: 18.0, right: 18.0), + child: Column( + children: [ + ZeroNetAppBar(), + Padding( + padding: EdgeInsets.only(bottom: 30), + ), + ZeroNetStatusWidget(), + Padding( + padding: EdgeInsets.only(bottom: 15), + ), + PopularZeroNetSites(), + Padding( + padding: EdgeInsets.only(bottom: 5), + ), + InAppUpdateWidget(), + Padding( + padding: EdgeInsets.only(bottom: 15), + ), + RatingButtonWidget(), + Padding( + padding: EdgeInsets.only(bottom: 15), + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +class InAppUpdateWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Observer(builder: (context) { + if (uiStore.appUpdate != AppUpdate.NOT_AVAILABLE) + return Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + 'App Update Available : ', + style: GoogleFonts.roboto( + fontSize: 20.0, + fontWeight: FontWeight.w500, + ), + ), + RaisedButton( + onPressed: uiStore.appUpdate.action, + color: Color(0xFF008297), + padding: EdgeInsets.only(left: 10, right: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30.0), + ), + child: Observer(builder: (context) { + return Text( + uiStore.appUpdate.text, + style: GoogleFonts.roboto( + fontSize: 20.0, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ); + }), + ), + ], + ); + return Container(); + }); + } +} + +class RatingButtonWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return RaisedButton( + onPressed: () async { + final InAppReview inAppReview = InAppReview.instance; + if (await inAppReview.isAvailable()) { + inAppReview.requestReview(); + } + }, + color: Color(0xFF008297), + padding: EdgeInsets.only(top: 10, bottom: 10, left: 30, right: 30), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30.0), + ), + child: Text( + 'Give Your Rating/Feedback', + style: GoogleFonts.roboto( + fontSize: 16.0, + fontWeight: FontWeight.normal, + color: Colors.white, + ), + ), + ); + } +} + +class ZeroNetStatusWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + Text( + 'Status', + style: GoogleFonts.roboto( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + Spacer( + flex: 1, + ), + Observer( + builder: (context) { + return Chip( + label: Padding( + padding: const EdgeInsets.all(2.0), + child: Text( + uiStore.zeroNetStatus.message, + style: GoogleFonts.roboto( + fontSize: 20, + color: Colors.white, + ), + ), + ), + backgroundColor: uiStore.zeroNetStatus.statusChipColor, + ); + }, + ), + Spacer( + flex: 1, + ), + Observer(builder: (context) { + return InkWell( + onTap: uiStore.zeroNetStatus.onAction, + child: Chip( + label: Padding( + padding: const EdgeInsets.all(2.0), + child: Text( + uiStore.zeroNetStatus.actionText, + style: GoogleFonts.roboto( + fontSize: 20, + color: Colors.white, + ), + ), + ), + backgroundColor: uiStore.zeroNetStatus.actionBtnColor, + ), + ); + }), + if (uiStore.zeroNetStatus == ZeroNetStatus.ERROR) + Spacer( + flex: 1, + ), + if (uiStore.zeroNetStatus == ZeroNetStatus.ERROR) + InkWell( + onTap: ZeroNetStatus.NOT_RUNNING.onAction, + child: Chip( + label: Padding( + padding: const EdgeInsets.all(2.0), + child: Text( + ZeroNetStatus.NOT_RUNNING.actionText, + style: GoogleFonts.roboto( + fontSize: 20, + color: Colors.white, + ), + ), + ), + backgroundColor: ZeroNetStatus.NOT_RUNNING.actionBtnColor, + ), + ), + Spacer( + flex: 4, + ), + ], + ), + ], + ); + } +} + +class PopularZeroNetSites extends StatelessWidget { + const PopularZeroNetSites({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + List zeroSites = []; + for (var key in Utils.initialSites.keys) { + var name = key; + zeroSites.add( + SiteDetailCard(name: name), + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Row( + children: [ + Column( + children: [ + Padding( + padding: EdgeInsets.all(4.0), + child: Text( + 'Popular Sites', + style: GoogleFonts.roboto( + fontSize: 20.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Container( + height: 3, + width: 120, + color: Color(0xFF2B2BFF), + ) + ], + ), + ], + ), + ListView( + shrinkWrap: true, + children: zeroSites, + physics: BouncingScrollPhysics(), + ) + // wgt, + ], + ); + } +} + +class SiteDetailCard extends StatelessWidget { + const SiteDetailCard({ + Key key, + @required this.name, + }) : super(key: key); + + final String name; + + @override + Widget build(BuildContext context) { + bool isZiteExists = isZiteExitsLocally( + Utils.initialSites[name]['btcAddress'], + ); + return Card( + shadowColor: Color(0x52000000), + elevation: 8.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(9), + ), + margin: EdgeInsets.only(bottom: 14.0), + child: Container( + height: 60.0, + child: Padding( + padding: const EdgeInsets.only( + left: 30.0, + right: 16.5, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + Text( + name, + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 18.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + Row( + children: [ + IconButton( + icon: Icon( + Icons.info_outline, + size: 28, + color: Color(0xFF5A53FF), + ), + onPressed: () { + uiStore.currentBottomSheetController = showBottomSheet( + context: context, + elevation: 16.0, + builder: (ctx) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.0), + ), + constraints: BoxConstraints( + minHeight: 300.0, + ), + width: MediaQuery.of(context).size.width, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(top: 3.0), + ), + Container( + height: 5.0, + width: 80.0, + margin: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Color(0xFF5A53FF), + borderRadius: BorderRadius.circular(25.0), + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: SiteDetailsSheet(name), + ), + ], + ), + ); + }, + ); + }, + ), + InkWell( + child: Icon( + isZiteExists ? Icons.play_arrow : Icons.file_download, + size: 36, + color: Color(isZiteExists ? 0xFF6EB69E : 0xDF6EB69E), + ), + onTap: () { + browserUrl = zeroNetUrl + Utils.initialSites[name]['url']; + uiStore.updateCurrentAppRoute(AppRoute.ZeroBrowser); + }, + ), + ], + ) + ], + ), + ), + ), + ); + } +} + +class SiteDetailsSheet extends StatelessWidget { + SiteDetailsSheet(this.name); + final String name; + @override + Widget build(BuildContext context) { + List sites = sitesAvailable.keys + .toList() + .where((element) => element == Utils.initialSites[name]['btcAddress']) + .toList(); + Site currentSite = Site(); + if (sites.length > 0) { + currentSite = sitesAvailable[sites[0]] + ..address = Utils.initialSites[name]['btcAddress']; + uiStore.updateCurrentSiteInfo( + SiteInfo().fromSite(currentSite), + ); + } + bool isZiteExists = isZiteExitsLocally( + Utils.initialSites[name]['btcAddress'], + ); + return Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + name, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 31.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Row( + children: [ + IconButton( + icon: Icon( + Icons.share, + color: Color(0xFF5A53FF), + ), + onPressed: () => Share.share( + Utils.initialSites[name]['url'], + ), + ), + RaisedButton( + color: Color(0xFF009764), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0)), + onPressed: () { + browserUrl = + zeroNetUrl + Utils.initialSites[name]['url']; + uiStore.currentBottomSheetController?.close(); + uiStore.updateCurrentAppRoute(AppRoute.ZeroBrowser); + }, + child: Text( + isZiteExists ? 'OPEN' : 'DOWNLOAD', + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 18.0, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ], + ) + ], + ), + Padding(padding: EdgeInsets.all(6.0)), + Text( + Utils.initialSites[name]['description'], + style: GoogleFonts.roboto( + fontSize: 16.0, + fontWeight: FontWeight.normal, + ), + ), + Padding(padding: EdgeInsets.all(6.0)), + Wrap( + spacing: 16.0, + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + children: [ + RaisedButton( + color: Color(0xFF008297), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0)), + onPressed: () async { + File logoFile = File(getZeroNetDataDir().path + + "/${Utils.initialSites[name]['btcAddress']}/img/logo.png"); + String logoPath = ''; + if (logoFile.existsSync()) { + logoPath = logoFile.path; + } else { + //TODO: Default logo quality is very low, use inbuilt logo for this. + } + var added = await addToHomeScreen( + name, + Utils.initialSites[name]['url'], + logoPath, + ); + if (added) { + uiStore.updateShowSnackReply(true); + } + }, + child: Text( + 'Add to HomeScreen', + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 18.0, + fontWeight: FontWeight.w300, + color: Colors.white, + ), + ), + ), + if (isZiteExists) + RaisedButton( + color: Color(0xFF517184), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0)), + onPressed: () { + uiStore.currentBottomSheetController?.close(); + uiStore.updateCurrentAppRoute(AppRoute.LogPage); + }, + child: Text( + 'Show Log', + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 18.0, + fontWeight: FontWeight.w300, + color: Colors.white, + ), + ), + ), + if (!unImplementedFeatures.contains(Feature.SITE_PAUSE_RESUME)) + if (isZiteExists && currentSite != null) + RaisedButton( + color: Color(0xFF009793), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0)), + onPressed: () { + //TODO: Implement this function; + currentSite = (currentSite.serving) + ? currentSite.pause() + : currentSite.resume(); + sitesAvailable[currentSite.address] = currentSite; + SiteManager().saveSettingstoFile( + File(getZeroNetDataDir().path + '/sites.json'), + sitesAvailable); + }, + child: Text( + currentSite.serving ? 'Pause' : 'Resume', + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 18.0, + fontWeight: FontWeight.w300, + color: Colors.white, + ), + ), + ), + if (!unImplementedFeatures.contains(Feature.SITE_DELETE)) + if (isZiteExists && currentSite != null) + RaisedButton( + color: Color(0xFFBB4848), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0)), + onPressed: () { + //TODO: Implement this function; + }, + child: Text( + 'Delete Zite', + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 18.0, + fontWeight: FontWeight.w300, + color: Colors.white, + ), + ), + ), + ], + ), + Padding(padding: EdgeInsets.all(6.0)), + if (isZiteExists) + Observer(builder: (context) { + return SiteInfoWidget( + uiStore.currentSiteInfo, + ); + }), + ], + ), + Align( + alignment: Alignment.bottomCenter, + child: Observer(builder: (context) { + Timer(Duration(seconds: 3), () { + uiStore.updateShowSnackReply(false); + }); + return uiStore.showSnackReply + ? Container( + height: 50.0, + color: Colors.grey[900], + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + '$name shortcut added to HomeScreen', + style: TextStyle(color: Colors.white), + ), + ), + ), + ) + : Container(); + }), + ), + ], + ); + } +} + +class SiteInfoWidget extends StatelessWidget { + final SiteInfo siteInfo; + const SiteInfoWidget(this.siteInfo); + @override + Widget build(BuildContext context) { + List infoTitleWgts = []; + List infoWgts = []; + for (var item in siteInfo.propStrings..remove('files')) { + final i = siteInfo.propStrings.indexOf(item); + infoTitleWgts.add( + Text( + siteInfo.propStrings[i].inCaps, + style: GoogleFonts.roboto( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + if (siteInfo.props[i] != null) { + String details = (siteInfo.props[i] is DateTime) + ? timeAgo.format(siteInfo.props[i]) + : siteInfo.props[i].toString(); + if (item == 'size') { + details = ((siteInfo.props[i] as int) ~/ 1000).toString() + + ' KB ($details Bytes)' + //TODO : Enable this when we read sites content.json file. + // +' (' + + // siteInfo.props[siteInfo.propStrings.indexOf('files')].toString() + + // ' Files)' + ; + } else if (siteInfo.props[i] is DateTime) { + DateTime t = siteInfo.props[i]; + details = details + ' (${t.day}/${t.month}/${t.year})'; + } + infoWgts.add( + Text( + ' : ' + details, + style: GoogleFonts.roboto( + fontSize: 12.0, + fontWeight: FontWeight.normal, + ), + ), + ); + } + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'SiteInfo', + style: GoogleFonts.roboto( + fontSize: 21.0, + fontWeight: FontWeight.w500, + ), + ), + Padding(padding: EdgeInsets.all(2.0)), + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: []..addAll(infoTitleWgts), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: []..addAll(infoWgts), + ), + ], + ) + ], + ); + } +} diff --git a/lib/widgets/log_page.dart b/lib/widgets/log_page.dart new file mode 100644 index 0000000..9342c47 --- /dev/null +++ b/lib/widgets/log_page.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:zeronet/mobx/varstore.dart'; +import 'package:zeronet/widgets/common.dart'; + +class ZeroNetLogPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding(padding: EdgeInsets.all(24)), + Padding( + padding: const EdgeInsets.only(left: 18.0, right: 18.0), + child: Column( + children: [ + ZeroNetAppBar(), + Padding( + padding: EdgeInsets.only(bottom: 30), + ), + Container( + height: MediaQuery.of(context).size.height * 0.83, + child: SingleChildScrollView( + child: Observer( + builder: (_) => Text(varStore.zeroNetLog), + ), + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/widgets/my_app.dart b/lib/widgets/my_app.dart deleted file mode 100644 index e367307..0000000 --- a/lib/widgets/my_app.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_mobx/flutter_mobx.dart'; - -import '../mobx/varstore.dart'; -import 'loading_page.dart'; -import 'myhome_page.dart'; - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'ZeroNet Mobile', - debugShowCheckedModeBanner: false, - theme: ThemeData( - primarySwatch: Colors.indigo, - ), - home: Observer( - builder: (context) { - if (varStore.zeroNetInstalled) return MyHomePage(); - return Loading(); - }, - ), - ); - } -} diff --git a/lib/widgets/myhome_page.dart b/lib/widgets/myhome_page.dart deleted file mode 100644 index e24ab4d..0000000 --- a/lib/widgets/myhome_page.dart +++ /dev/null @@ -1,509 +0,0 @@ -import 'dart:io'; - -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; -import 'package:share/share.dart'; -import 'package:zeronet_ws/zeronet_ws.dart'; - -import '../others/common.dart'; -import '../others/constants.dart'; -import '../models/models.dart'; -import '../mobx/varstore.dart'; -import '../others/native.dart'; -import '../others/utils.dart'; -import '../others/zeronet_utils.dart'; -import 'settings_page.dart'; - -class MyHomePage extends StatefulWidget { - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - bool showLog = false; - - @override - initState() { - checkInitStatus(); - if (firstTime) { - viewSettings = true; - makeExecHelper(); - } - flutterWebViewPlugin.onUrlChanged.listen((newUrl) => browserUrl = newUrl); - super.initState(); - } - - _reload() => this.mounted ? setState(() {}) : null; - - checkInitStatus() async { - try { - String url = defZeroNetUrl + Utils.initialSites['ZeroMobile']['url']; - String key = await ZeroNet.instance.getWrapperKey(url); - zeroNetUrl = defZeroNetUrl; - varStore.zeroNetWrapperKey = key; - varStore.setZeroNetStatus('Running'); - ZeroNet.instance.connect( - zeroNetIPwithPort(defZeroNetUrl), - Utils.urlHello, - ); - showZeroNetRunningNotification(enableVibration: false); - testUrl(); - } catch (e) { - if (!firstTime && - (varStore.settings[autoStartZeroNet] as ToggleSetting).value == true) - runZeroNet(); - if (e is OSError) { - if (e.errorCode == 111) { - printToConsole('Zeronet Not Running'); - } - } - } - } - - bool viewBrowser = false; - bool viewSettings = false; - -// ignore: prefer_collection_literals - final Set jsChannels = [ - JavascriptChannel( - name: 'Print', - onMessageReceived: (JavascriptMessage message) { - printOut(message.message); - }, - ), - ].toSet(); - final flutterWebViewPlugin = FlutterWebviewPlugin(); - - void setAppBarTitle() { - if (!viewBrowser) { - if (showLog) - varStore.setZeroNetAppbarStatus('ZeroNet Log'); - else if (viewSettings) - varStore.setZeroNetAppbarStatus('App Settings'); - else - varStore.setZeroNetAppbarStatus('ZeroNet Mobile'); - } else - varStore.setZeroNetAppbarStatus('ZeroNet Browser'); - } - - AppBar appBar() { - return AppBar( - title: Observer( - builder: (context) { - return Text(varStore.zeroNetAppbarStatus); - }, - ), - actions: [ - if (!viewBrowser && !viewSettings) - IconButton( - icon: Icon(Icons.settings_applications), - onPressed: () { - viewSettings = !viewSettings; - setAppBarTitle(); - _reload(); - }, - ), - if ((varStore.settings[enableZeroNetConsole] as ToggleSetting).value) - if (!viewBrowser && !viewSettings) - IconButton( - icon: Icon(Icons.content_paste), - onPressed: () { - showLog = !showLog; - setAppBarTitle(); - _reload(); - }, - ), - if (!viewBrowser && !viewSettings) - IconButton( - icon: Icon(Icons.open_in_browser), - onPressed: () { - viewBrowser = !viewBrowser; - setAppBarTitle(); - _reload(); - }, - ), - if (viewBrowser || viewSettings) - IconButton( - icon: Icon(Icons.home), - onPressed: () { - if (viewBrowser) { - viewBrowser = !viewBrowser; - } else { - viewSettings = !viewSettings; - } - flutterWebViewPlugin.close(); - setAppBarTitle(); - _reload(); - }, - ), - ], - ); - } - - Widget webView() { - return Stack( - children: [ - Padding( - padding: const EdgeInsets.only(top: 0.0), //80.0 - child: WebviewScaffold( - url: browserUrl, - javascriptChannels: jsChannels, - mediaPlaybackRequiresUserGesture: false, - appCacheEnabled: true, - appBar: appBar(), - withZoom: true, - useWideViewPort: true, - withLocalStorage: true, - hidden: true, - initialChild: Container( - // color: Colors.white, - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: CircularProgressIndicator(), - ), - Text('Loading.....'), - ], - ), - ), - ), - bottomNavigationBar: BottomAppBar( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - IconButton( - icon: const Icon(Icons.share), - onPressed: () => Share.share(browserUrl), - ), - IconButton( - icon: const Icon(Icons.arrow_back_ios), - onPressed: () { - flutterWebViewPlugin.goBack(); - }, - ), - IconButton( - icon: const Icon(Icons.arrow_forward_ios), - onPressed: () { - flutterWebViewPlugin.goForward(); - }, - ), - IconButton( - icon: const Icon(Icons.autorenew), - onPressed: () { - flutterWebViewPlugin.reload(); - }, - ), - ], - ), - ), - ), - ), - ], - ); - } - - @override - Widget build(BuildContext context) { - if ((varStore.settings[enableFullScreenOnWebView] as ToggleSetting) - ?.value == - true) { - if (viewBrowser) - SystemChrome.setEnabledSystemUIOverlays([]); - else - SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); - } else - SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); - return viewBrowser - ? WillPopScope( - onWillPop: () { - viewBrowser = false; - _reload(); - return Future.value(false); - }, - child: webView(), - ) - : Scaffold( - appBar: appBar(), - body: viewSettings - ? SettingsPage() - : SingleChildScrollView( - child: showLog - ? Observer( - builder: (_) => Text(varStore.zeroNetLog), - ) - : Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: const Text( - 'Status', - style: TextStyle( - fontSize: 24, - ), - ), - ), - Padding( - padding: const EdgeInsets.all(2.0), - child: Observer( - builder: (_) { - bool isRunning = - varStore.zeroNetStatus == 'Running'; - if (isRunning) - ZeroNet.instance.connect( - zeroNetIPwithPort(zeroNetUrl), - Utils.urlHello, - ); - return Row( - children: [ - Padding( - padding: const EdgeInsets.all( - 6.0, - ), - child: Chip( - backgroundColor: isRunning - ? Colors.greenAccent - : varStore.zeroNetStatus == - 'Not Running' - ? Colors.grey - : Colors.orange, - padding: const EdgeInsets.all( - 8.0, - ), - label: Text( - varStore.zeroNetStatus, - ), - ), - ), - if (isRunning) - GestureDetector( - child: const Padding( - padding: const EdgeInsets.all( - 4.0, - ), - child: Chip( - backgroundColor: Colors.red, - padding: - const EdgeInsets.all( - 8.0, - ), - label: const Text( - 'Stop', - ), - ), - ), - onTap: shutDownZeronet, - ), - if (isRunning) - GestureDetector( - child: const Padding( - padding: const EdgeInsets.all( - 4.0, - ), - child: Chip( - backgroundColor: - Colors.blueAccent, - padding: EdgeInsets.all( - 8.0, - ), - label: const Text( - 'More Info', - ), - ), - ), - onTap: () { - ZeroNet.instance.siteInfo( - callback: (msg) { - //TODO: Show Info in List Form. - showDialogC( - context: context, - title: 'ZeroNetInfo', - body: msg, - ); - }); - }, - ), - ], - ); - }, - ), - ), - ], - ), - Observer( - builder: (context) { - if (varStore.zeroNetStatus == 'Not Running') - return Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Text( - 'Hint : ZeroNet is Not Running click on Play Button at the Bottom'), - ); - return Container(); - }, - ), - PopularZeroNetSites( - callback: () { - viewBrowser = true; - setState(() {}); - }, - ), - ], - ), - ), - floatingActionButton: (viewSettings) - ? null - : FloatingActionButton( - child: Observer( - builder: (c) { - if (varStore.zeroNetStatus == 'Not Running') - return Icon(Icons.play_arrow); - return Icon(Icons.stop); - }, - ), - onPressed: (varStore.zeroNetStatus == 'Not Running') - ? runZeroNet - : shutDownZeronet, - ), - ); - } -} - -class PopularZeroNetSites extends StatelessWidget { - final VoidCallback callback; - const PopularZeroNetSites({ - Key key, - this.callback, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - List zeroSites = []; - for (var key in Utils.initialSites.keys) { - var name = key; - var description = Utils.initialSites[key]['description']; - var url = Utils.initialSites[key]['url']; - var i = Utils.initialSites.keys.toList().indexOf(key); - zeroSites.add( - Container( - height: 185, - width: 185, - margin: EdgeInsets.all(8.0), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - colors[i], - colors[i + 1], - ], - ), - ), - child: Padding( - padding: EdgeInsets.all(8.0), - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - top: 24.0, - left: 15.0, - ), - child: AutoSizeText( - name, - maxLines: 1, - style: TextStyle( - color: Colors.white, - fontSize: 28.0, - ), - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 15.0, - ), - child: AutoSizeText( - description, - maxLines: 2, - style: TextStyle( - color: Colors.white, - fontSize: 16.0, - ), - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.only( - bottom: 8.0, - right: 12.0, - ), - child: Align( - alignment: Alignment.bottomRight, - child: Observer(builder: (context) { - return OutlineButton( - borderSide: BorderSide(color: Colors.white), - child: Text( - 'Open', - style: TextStyle( - color: (varStore.zeroNetStatus == 'Running') - ? Colors.white - : Theme.of(context).disabledColor, - ), - ), - onPressed: (varStore.zeroNetStatus == 'Running') - ? () { - if (varStore.zeroNetStatus == 'Running') { - browserUrl = zeroNetUrl + url; - callback(); - } - } - : null, - ); - }), - ), - ), - ], - ), - ), - ), - ); - } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.all(8.0), - child: Text( - 'Popular Sites', - style: TextStyle( - fontSize: 24, - ), - ), - ), - GridView.count( - shrinkWrap: true, - crossAxisCount: 2, - children: zeroSites, - physics: BouncingScrollPhysics(), - ) - ]..add( - Padding( - padding: EdgeInsets.all(40.0), - ), - ), - ); - } -} diff --git a/lib/widgets/settings_page.dart b/lib/widgets/settings_page.dart index 12eb36d..01e9bb4 100644 --- a/lib/widgets/settings_page.dart +++ b/lib/widgets/settings_page.dart @@ -1,295 +1,259 @@ import 'dart:io'; -import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:zeronet/mobx/uistore.dart'; +import 'package:zeronet/mobx/varstore.dart'; +import 'package:zeronet/models/models.dart'; +import 'package:zeronet/others/common.dart'; +import 'package:zeronet/others/constants.dart'; +import 'package:zeronet/others/zeronet_utils.dart'; import 'package:zeronet_ws/zeronet_ws.dart'; -import '../mobx/varstore.dart'; -import '../others/common.dart'; -import '../others/constants.dart'; -import '../models/models.dart'; -import '../others/native.dart'; -import '../others/utils.dart'; -import '../others/zeronet_utils.dart'; +import 'common.dart'; -class SettingsPage extends StatefulWidget { +class SettingsPage extends StatelessWidget { @override - _SettingsPageState createState() => _SettingsPageState(); + Widget build(BuildContext context) { + return Container( + height: MediaQuery.of(context).size.height, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Padding(padding: EdgeInsets.all(24)), + Padding( + padding: const EdgeInsets.only(left: 18.0, right: 18.0), + child: Column( + children: [ + ZeroNetAppBar(), + // Padding( + // padding: EdgeInsets.only(bottom: 30), + // ), + ListView.builder( + physics: BouncingScrollPhysics(), + shrinkWrap: true, + itemCount: Utils().defSettings.keys.length, + itemBuilder: (ctx, i) { + Setting current = Utils() + .defSettings[Utils().defSettings.keys.toList()[i]]; + return SettingsCard( + setting: current, + ); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } } -class _SettingsPageState extends State { - _reload() => this.mounted ? setState(() {}) : null; +class SettingsCard extends StatelessWidget { + const SettingsCard({ + Key key, + @required this.setting, + }) : super(key: key); + + final Setting setting; @override Widget build(BuildContext context) { - List wrapChildren = [ - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: GestureDetector( - child: Chip( - label: Text('Create New Profile'), - ), - onTap: () { - if (isZeroNetUserDataExists()) { - showDialogW( - context: context, - title: 'Provide A Name for Existing Profile', - body: ProfileSwitcherUserNameEditText(), - actionOk: Row( - children: [ - FlatButton( - child: Text('Create'), - onPressed: () { - if (username.isNotEmpty) { - File file = File(getZeroNetUsersFilePath()); - var f = file.renameSync(getZeroNetDataDir().path + - '/users-$username.json'); - if (f.existsSync()) { - if (file.existsSync()) file.deleteSync(); - Navigator.pop(context); - ZeroNet.instance.shutDown(); - runZeroNet(); - } - username = ''; - _reload(); - } else { - validUsername = false; - _reload(); - } - }, - ), - FlatButton( - child: Text('Backup'), - onPressed: () => backUpUserJsonFile(context), - ), - ], - ), - ); - } else - zeronetNotInit(context); - }, - ), + return Card( + shadowColor: Color(0x52000000), + elevation: 8.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(9), ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: GestureDetector( - child: Chip( - label: Text('Import Profile'), - ), - onTap: () async { - var file = await getUserJsonFile(); - if (file != null && file.path.endsWith('users.json')) { - var isSameUser = file.existsSync() - ? getZeroNetUsersFilePath() == file.path - : false; - showDialogW( - context: context, - title: 'Restore Profile ?', - body: Text( - 'this will delete the existing profile, ' - 'backup existing profile using backup button below\n\n' - 'Selected Userfile : \n' - '$filePath' - '\n\n${isSameUser ? 'You can only select users.json file, outside zeronet data folder' : ''}', - ), - actionOk: Row( - children: [ - FlatButton( - onPressed: isSameUser - ? null - : () async { - File f = File(getZeroNetUsersFilePath()); - printOut(f.path); - if (!isSameUser) { - if (f.existsSync()) f.deleteSync(); - f.createSync(); - f.writeAsStringSync(file.readAsStringSync()); - _reload(); - try { - ZeroNet.instance.shutDown(); - } catch (e) { - printOut(e); - } - runZeroNet(); - Navigator.pop(context); - } - }, - child: Text( - 'Restore', - ), - ), - FlatButton( - child: Text('Backup'), - onPressed: () => backUpUserJsonFile(context), - ), - ], - ), - ); - } - }, - ), - ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: GestureDetector( - child: Chip( - label: Text('Backup Profile'), - ), - onTap: () => backUpUserJsonFile(context), + margin: EdgeInsets.only(bottom: 14.0), + child: Container( + // height: 60.0, + constraints: BoxConstraints( + minHeight: 60.0, ), - ), - ]; - getZeroNameProfiles().forEach((profile) { - wrapChildren.insert( - 0, - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: GestureDetector( - child: Chip( - label: Text(profile), - ), - onTap: () { - showDialogW( - context: context, - title: 'Switch Profile to $profile ?', - body: Text( - 'this will delete the existing profile, ' - 'backup existing profile using backup button below', - ), - actionOk: profileSwitcherActionOk(profile, context), - ); - }, + child: Padding( + padding: const EdgeInsets.only( + top: 6.0, + bottom: 6.0, + left: 18.0, + right: 16.5, ), - ), - ); - }); - return ListView.builder( - itemCount: defSettings.keys.length, - itemBuilder: (c, i) { - var key = defSettings.keys.toList()[i]; - var map = defSettings; - if (firstTime && key == profileSwitcher) return Container(); - return Padding( - padding: const EdgeInsets.all(8.0), - child: Card( - elevation: 10.0, - child: Container( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AutoSizeText( - map[key].name, - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - maxLines: 1, - minFontSize: 18, - maxFontSize: 24, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + Text( + setting.name, + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 16.0, + fontWeight: FontWeight.w500, ), - if (map[key] is ToggleSetting) - Observer( - builder: (context) { - var map = varStore.settings; - //TODO: Check this, if it is removing non toggle settings from settings file - map.removeWhere((e, w) => !(w is ToggleSetting)); - return Switch( - value: - (map[key] as ToggleSetting)?.value ?? false, - onChanged: - (map[key] as ToggleSetting).onChanged, + ), + ], + ), + Row( + children: [ + IconButton( + icon: Icon( + Icons.info_outline, + size: 28, + color: Color(0xFF5A53FF), + ), + onPressed: () { + showBottomSheet( + context: context, + elevation: 16.0, + builder: (ctx) { + return + // Card( + // color: Color(0xFFFCFCFC), + // borderOnForeground: false, + // elevation: 16.0, + // child: + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.0), + ), + constraints: BoxConstraints( + minHeight: 250.0, + ), + width: MediaQuery.of(context).size.width, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(top: 3.0), + ), + Container( + height: 5.0, + width: 80.0, + margin: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Color(0xFF5A53FF), + borderRadius: + BorderRadius.circular(25.0), + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: SettingDetailsSheet(setting), + ), + ], + ), + // ), + // shadowColor: Colors.grey, ); }, + ); + }, + ), + if (setting is ToggleSetting) + Observer( + builder: (context) { + return Switch( + value: (varStore.settings[ + (setting as ToggleSetting).name] + as ToggleSetting) + .value, + activeColor: Color(0xFF5380FF), + onChanged: (setting as ToggleSetting).onChanged, + ); + }, + ) + ], + ) + ], + ), + if (setting is MapSetting) + Observer(builder: (ctx) { + var i = uiStore.reload; + List children = []; + var settingL = setting as MapSetting; + settingL.options.forEach((element) { + children.add( + InkWell( + borderRadius: BorderRadius.circular(24.0), + splashColor: Color(0xFF5380FF), + onTap: () { + settingL.options[settingL.options.indexOf(element)] + .onClick(ctx); + }, + child: Padding( + padding: const EdgeInsets.only(left: 3.0, right: 3.0), + child: Chip( + elevation: 2.0, + label: Text( + settingL + .options[settingL.options.indexOf(element)] + .description, + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), ), - ], - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - map[key].description, - style: TextStyle( - fontSize: 16.0, ), ), - ), - if (map[key] is MapSetting) - if (key == profileSwitcher) - ((map[key] as MapSetting).map['selected'] as String) - .isEmpty - ? Wrap( - children: wrapChildren, - ) - : GestureDetector( - child: Chip( - label: Text('Create Profile'), - ), - onTap: () {}, - ) - else if (key == pluginManager) - Wrap( - children: [ - GestureDetector( - child: Chip( - label: Text('Open Plugin Manager'), + ); + }); + if ((setting as MapSetting).name == profileSwitcher) + getZeroNameProfiles().forEach((profile) { + children.insert( + 0, + InkWell( + borderRadius: BorderRadius.circular(24.0), + splashColor: Color(0xFF5380FF), + onTap: () { + showDialogW( + context: context, + title: 'Switch Profile to $profile ?', + body: Text( + 'this will delete the existing profile, ' + 'backup existing profile using backup button below', ), - onTap: () { - showDialogW( - context: context, - title: pluginManager, - body: PluginManager(), - actionOk: FlatButton( - onPressed: () { - ZeroNet.instance.shutDown(); - runZeroNet(); - Navigator.pop(context); - }, - child: Text('Restart'), - ), - ); - }, - ), - Container( - margin: const EdgeInsets.only(left: 8.0), - child: GestureDetector( - child: Chip( - label: Text('Load Plugin'), + actionOk: + profileSwitcherActionOk(profile, context), + ); + }, + child: Padding( + padding: + const EdgeInsets.only(left: 3.0, right: 3.0), + child: Chip( + elevation: 2.0, + label: Text( + profile, + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 14.0, + fontWeight: FontWeight.w500, ), - onTap: () { - showDialogW( - context: context, - title: 'Install a Plugin', - body: Text( - 'This will load plugin to your ZeroNet repo, ' - '\nWarning : Loading Unknown/Untrusted plugins may compromise ZeroNet Installation.'), - actionOk: FlatButton( - onPressed: () async { - var file = await getPluginZipFile(); - if (file != null) { - Navigator.pop(context); - installPluginDialog(file, context); - } - }, - child: Text('Install'), - ), - ); - }, ), ), - ], - ) - ], - ), - ), - ), + ), + ), + ); + }); + return Wrap( + children: children, + ); + }) + ], ), - ); - }, + ), + ), ); } @@ -305,7 +269,7 @@ class _SettingsPageState extends State { File file = File(getZeroNetDataDir().path + '/users-$profile.json'); if (file.existsSync()) { file.renameSync(getZeroNetDataDir().path + '/users.json'); - _reload(); + // _reload(); ZeroNet.instance.shutDown(); runZeroNet(); Navigator.pop(context); @@ -324,138 +288,33 @@ class _SettingsPageState extends State { } } -class PluginManager extends StatefulWidget { - const PluginManager({ - Key key, - }) : super(key: key); - - @override - _PluginManagerState createState() => _PluginManagerState(); -} - -class _PluginManagerState extends State { - _reload() => this.mounted ? setState(() {}) : null; - +class SettingDetailsSheet extends StatelessWidget { + SettingDetailsSheet(this.setting); + final Setting setting; @override Widget build(BuildContext context) { - var size = MediaQuery.of(context).size; - List plugins = []; - var pluginsPath = zeroNetDir + '/plugins/'; - Directory(pluginsPath).listSync().forEach((entity) { - var pycacheDir = entity.path.endsWith('__pycache__'); - if (entity is Directory && !pycacheDir) { - printOut(entity.path); - plugins.insert(0, entity.path.replaceAll(pluginsPath, '')); - } - }); - plugins.sort(); - return Container( - height: size.height * 0.73, - width: size.width, - child: ListView.builder( - itemCount: plugins.length, - itemBuilder: (ctx, i) { - final isDisabled = plugins[i].startsWith('disabled-'); - final pluginName = isDisabled - ? plugins[i].replaceFirst('disabled-', '') - : plugins[i]; - return Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(pluginName), - Switch( - onChanged: (value) { - if (isDisabled) - Directory(pluginsPath + plugins[i]) - .renameSync(pluginsPath + pluginName); - else - Directory(pluginsPath + plugins[i]) - .renameSync(pluginsPath + 'disabled-' + plugins[i]); - _reload(); - }, - value: !isDisabled, - ) - ], - ); - }, - ), - ); - } -} - -var username = ''; -var errorText = ''; -var validUsername = false; - -class ProfileSwitcherUserNameEditText extends StatefulWidget { - @override - _ProfileSwitcherUserNameEditTextState createState() => - _ProfileSwitcherUserNameEditTextState(); -} - -class _ProfileSwitcherUserNameEditTextState - extends State { - TextEditingController _controller = TextEditingController(); - - @override - void initState() { - final usrName = getZeroIdUserName(); - _controller.text = usrName; - if (usrName.isNotEmpty) { - username = usrName; - } - super.initState(); - } - - @override - Widget build(BuildContext context) { - return ListBody( - children: [ - Text( - 'Always remember to backup users.json before doing anything because, ' - 'we are not able to tell when a software will fail. ' - 'Click Backup below to backup your Existing users.json file.\n', - style: TextStyle( - color: Colors.red, - ), - ), - Text('Username Phrase :'), - Padding( - padding: const EdgeInsets.only( - left: 8.0, - ), - child: TextField( - controller: _controller, - onChanged: (text) { - username = text; - var valid = text.isNotEmpty; - if (valid) { - if (text.contains(' ')) { - errorText = 'username can\'t contain spaces'; - valid = false; - } else if (text.length < 6) { - errorText = 'username can\'t be less than 6 characters.'; - valid = false; - } else if (File(getZeroNetDataDir().path + '/users-$text.json') - .existsSync()) { - errorText = 'username already exists, choose different one.'; - valid = false; - } - } else { - errorText = 'username can\'t be Empty'; - } - setState(() { - validUsername = valid; - }); - }, - style: TextStyle( - fontSize: 18.0, - ), - decoration: InputDecoration( - hintText: 'username', - errorText: validUsername ? null : errorText, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + setting.name, + maxLines: 1, + style: GoogleFonts.roboto( + fontSize: 24.0, + fontWeight: FontWeight.w500, + ), ), + ], + ), + Padding(padding: EdgeInsets.all(6.0)), + Text( + setting.description, + style: GoogleFonts.roboto( + fontSize: 16.0, + fontWeight: FontWeight.normal, ), ), ], diff --git a/lib/widgets/shortcut_loading_page.dart b/lib/widgets/shortcut_loading_page.dart new file mode 100644 index 0000000..d146c38 --- /dev/null +++ b/lib/widgets/shortcut_loading_page.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:zeronet/others/common.dart'; + +class ShortcutLoadingPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + switch (zeroBrowserTheme) { + case 'dark': + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.light, + systemNavigationBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.light, + statusBarColor: Colors.blueGrey[900], + systemNavigationBarColor: Colors.blueGrey[900], + ), + ); + break; + case 'light': + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.dark, + systemNavigationBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.dark, + statusBarColor: Colors.white, + systemNavigationBarColor: Colors.white, + ), + ); + break; + default: + } + return Container( + color: zeroBrowserTheme == 'dark' ? Colors.blueGrey[900] : Colors.white, + width: MediaQuery.of(context).size.width, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Image.asset('assets/logo.png'), + Padding( + padding: EdgeInsets.all(24.0), + ), + Text( + 'Loading...', + style: TextStyle( + fontSize: 24.0, + fontStyle: FontStyle.italic, + color: zeroBrowserTheme == 'light' + ? Colors.blueGrey[900] + : Colors.white, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/zerobrowser_page.dart b/lib/widgets/zerobrowser_page.dart new file mode 100644 index 0000000..c0946a7 --- /dev/null +++ b/lib/widgets/zerobrowser_page.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; +import 'package:share/share.dart'; +import 'package:zeronet/mobx/uistore.dart'; +import 'package:zeronet/mobx/varstore.dart'; +import 'package:zeronet/models/enums.dart'; +import 'package:zeronet/models/models.dart'; +import 'package:zeronet/others/common.dart'; +import 'package:zeronet/others/constants.dart'; +import 'package:zeronet/others/utils.dart'; + +class ZeroBrowser extends StatelessWidget { + setTheme() { + switch (zeroBrowserTheme) { + case 'dark': + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.light, + systemNavigationBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.light, + statusBarColor: Colors.blueGrey[900], + systemNavigationBarColor: Colors.blueGrey[900], + ), + ); + break; + case 'light': + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.dark, + systemNavigationBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.dark, + statusBarColor: Colors.white, + systemNavigationBarColor: Colors.white, + ), + ); + break; + default: + } + } + + @override + Widget build(BuildContext context) { + setTheme(); + bool fullScreenWebView = + (varStore.settings[enableFullScreenOnWebView] as ToggleSetting)?.value; + if (fullScreenWebView) SystemChrome.setEnabledSystemUIOverlays([]); + // ignore: prefer_collection_literals + final Set jsChannels = [ + JavascriptChannel( + name: 'Print', + onMessageReceived: (JavascriptMessage message) { + printOut(message.message); + }, + ), + ].toSet(); + final flutterWebViewPlugin = FlutterWebviewPlugin(); + flutterWebViewPlugin.onUrlChanged.listen((newUrl) => browserUrl = newUrl); + return WillPopScope( + onWillPop: () { + if (launchUrl.isNotEmpty) { + return Future.value(true); + } else { + SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarColor: Colors.white, + systemNavigationBarColor: Colors.white, + statusBarIconBrightness: Brightness.dark, + systemNavigationBarIconBrightness: Brightness.dark, + ), + ); + uiStore.updateCurrentAppRoute(AppRoute.Home); + return Future.value(false); + } + }, + child: SafeArea( + child: Stack( + children: [ + WebviewScaffold( + url: browserUrl, + javascriptChannels: jsChannels, + mediaPlaybackRequiresUserGesture: false, + appCacheEnabled: true, + withZoom: true, + useWideViewPort: true, + withLocalStorage: true, + hidden: true, + initialChild: Container( + color: zeroBrowserTheme == 'dark' + ? Colors.blueGrey[900] + : Colors.white, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), + Text( + 'Loading.....', + style: TextStyle( + color: zeroBrowserTheme == 'light' + ? Colors.blueGrey[900] + : Colors.white, + ), + ), + ], + ), + ), + ), + bottomNavigationBar: BottomAppBar( + color: zeroBrowserTheme == 'dark' + ? Colors.blueGrey[900] + : Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + icon: const Icon(Icons.home), + color: zeroBrowserTheme == 'light' + ? Colors.blueGrey[900] + : Colors.white, + onPressed: () { + SystemChrome.setEnabledSystemUIOverlays( + SystemUiOverlay.values); + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarColor: Colors.white, + systemNavigationBarColor: Colors.white, + statusBarIconBrightness: Brightness.dark, + systemNavigationBarIconBrightness: Brightness.dark, + ), + ); + uiStore.updateCurrentAppRoute(AppRoute.Home); + }, + ), + Spacer(), + IconButton( + icon: const Icon(Icons.share), + color: zeroBrowserTheme == 'light' + ? Colors.blueGrey[900] + : Colors.white, + onPressed: () => Share.share(browserUrl), + ), + IconButton( + icon: const Icon(Icons.arrow_back_ios), + color: zeroBrowserTheme == 'light' + ? Colors.blueGrey[900] + : Colors.white, + onPressed: () { + flutterWebViewPlugin.goBack(); + }, + ), + IconButton( + icon: const Icon(Icons.arrow_forward_ios), + color: zeroBrowserTheme == 'light' + ? Colors.blueGrey[900] + : Colors.white, + onPressed: () { + flutterWebViewPlugin.goForward(); + }, + ), + IconButton( + icon: const Icon(Icons.autorenew), + color: zeroBrowserTheme == 'light' + ? Colors.blueGrey[900] + : Colors.white, + onPressed: () { + flutterWebViewPlugin.reload(); + }, + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index d9d9bdd..13cbc1a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,11 +21,17 @@ dependencies: flutter_mobx: 0.3.3+3 http: 0.12.0+3 mobx: 0.3.3+1 - package_info: 0.4.0+16 + google_fonts: 1.1.0 + in_app_review: 0.2.0+4 + in_app_update: 1.1.11 + outline_material_icons: 0.1.1 + equatable: 1.2.5 + time_ago_provider: 2.0.5 + package_info: 0.4.3 path_provider: 1.6.5 random_string: 2.0.1 share: 0.6.3+6 - url_launcher: 5.2.7 + url_launcher: 5.5.1 flutter_webview_plugin: #0.3.11 git: url: https://github.com/canewsin/flutter_webview_plugin diff --git a/test/test.dart b/test/test.dart deleted file mode 100644 index 283aad6..0000000 --- a/test/test.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'dart:math'; -import 'dart:convert'; - -main(List args) { - for (var i = 0; i < 50; i++) { - var list = List.generate(32, (i) { - return Random().nextInt(100); - }); - print(utf8.decode(list)); - } -} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index abb018e..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,40 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:zeronet/widgets/my_app.dart'; -import 'package:zeronet/others/extensions.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); - - test('name', () { - var current = Directory.current; - var testFileName = 'pubspec.yaml'; - File f = File(current.path + '/$testFileName'); - expect(f.name(), testFileName); - }); -}