2022-05-30 13:25:06 +08:00
import ' dart:async ' ;
2022-08-17 21:28:36 +08:00
import ' dart:convert ' ;
2022-08-03 22:03:31 +08:00
import ' dart:io ' ;
2022-08-17 21:28:36 +08:00
import ' dart:typed_data ' ;
2022-05-30 13:25:06 +08:00
2022-08-17 21:28:36 +08:00
import ' package:back_button_interceptor/back_button_interceptor.dart ' ;
2022-08-09 19:32:19 +08:00
import ' package:desktop_multi_window/desktop_multi_window.dart ' ;
2022-04-14 15:37:47 +08:00
import ' package:flutter/gestures.dart ' ;
2020-11-15 20:04:05 +08:00
import ' package:flutter/material.dart ' ;
2022-08-16 21:27:21 +08:00
import ' package:flutter_hbb/desktop/widgets/tabbar_widget.dart ' ;
2022-06-13 21:07:26 +08:00
import ' package:get/instance_manager.dart ' ;
2022-07-29 16:47:24 +08:00
import ' package:shared_preferences/shared_preferences.dart ' ;
2022-08-09 09:01:06 +08:00
import ' package:window_manager/window_manager.dart ' ;
2022-04-19 13:07:45 +08:00
import ' models/model.dart ' ;
2022-08-03 22:03:31 +08:00
import ' models/platform_model.dart ' ;
2021-08-02 20:54:56 +08:00
2022-03-07 22:54:34 +08:00
final globalKey = GlobalKey < NavigatorState > ( ) ;
2022-04-12 22:38:39 +08:00
final navigationBarKey = GlobalKey ( ) ;
2022-03-07 22:54:34 +08:00
2022-08-03 22:03:31 +08:00
var isAndroid = Platform . isAndroid ;
var isIOS = Platform . isIOS ;
2022-03-17 21:03:52 +08:00
var isWeb = false ;
2022-05-23 16:02:37 +08:00
var isWebDesktop = false ;
2022-08-03 22:03:31 +08:00
var isDesktop = Platform . isWindows | | Platform . isMacOS | | Platform . isLinux ;
2022-03-17 21:03:52 +08:00
var version = " " ;
2022-03-24 17:58:33 +08:00
int androidVersion = 0 ;
2022-03-17 21:03:52 +08:00
2021-08-02 20:54:56 +08:00
typedef F = String Function ( String ) ;
2022-02-10 02:07:53 +08:00
typedef FMethod = String Function ( String , dynamic ) ;
2021-08-02 20:54:56 +08:00
2022-08-17 21:28:36 +08:00
final iconKeyboard = MemoryImage ( Uint8List . fromList ( base64Decode (
" iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAgVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9d3yJTAAAAKnRSTlMA0Gd/0y8ILZgbJffDPUwV2nvzt+TMqZxyU7CMb1pYQyzsvKunkXE4AwJnNC24AAAA+0lEQVQ4y83O2U7DMBCF4ZMxk9rZk26kpQs7nPd/QJy4EiLbLf01N5Y/2YP/qxDFQvGB5NPC/ZpVnfJx4b5xyGfF95rkHvNCWH1u+N6J6T0sC7gqRy8uGPfBLEbozPXUjlkQKwGaFPNizwQbwkx0TDvhCii34ExZCSQVBdzIOEOyeclSHgBGXkpeygXSQgStACtWx4Z8rr8COHOvfEP/IbbsQAToFUAAV1M408IIjIGYAPoCSNRP7DQutfQTqxuAiH7UUg1FaJR2AGrrx52sK2ye28LZ0wBAEyR6y8X+NADhm1B4fgiiHXbRrTrxpwEY9RdM9wsepnvFHfUDwYEeiwAJr/gAAAAASUVORK5CYII= " ) ) ) ;
final iconClipboard = MemoryImage ( Uint8List . fromList ( base64Decode (
' iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAjVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8DizOFAAAALnRSTlMAnIsyZy8YZF3NSAuabRL34cq6trCScyZ4qI9CQDwV+fPl2tnTwzkeB+m/pIFK/Xx0ewAAAQlJREFUOMudktduhDAQRWep69iY3tle0+7/f16Qg7MsJUQ5Dwh8jzRzhemJPIaf3GiW7eFQfOwDPp1ek/iMnKgBi5PrhJAhZAa1lCxE9pw5KWMswOMAQXuQOvqTB7tLFJ36wimKLrufZTzUaoRtdthqRA2vEwS+tR4qguiElRKk1YMrYfUQRkwLmwVBYDMvJKF8R0o3V2MOhNrfo+hXSYYjPn1L/S+n438t8gWh+q1F+cYFBMm1Jh8Ia7y2OWXQxMMRLqr2eTc1crSD84cWfEGwYM4LlaACEee2ZjsQXJxR3qmYb+GpC8ZfNM5oh3yxxbxgQE7lEkb3ZvvH1BiRHn1bu02ICcKGWr4AudUkyYxmvywAAAAASUVORK5CYII= ' ) ) ) ;
final iconAudio = MemoryImage ( Uint8List . fromList ( base64Decode (
' iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAk1BMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ROyVeAAAAMHRSTlMAgfz08DDqCAThvraZjEcoGA751JxzbGdfTRP25NrIpaGTcEM+HAvMuKinhXhWNx9Yzm/gAAABFUlEQVQ4y82S2XLCMAxFheMsQNghCQFalkL39vz/11V4GpNk0r629+Va1pmxPFfyh1ravOP2Y1ydJmBO0lYP3r+PyQ62s2Y7fgF6VRXOYdToT++ogIuoVhCUtX7YpwJG3F8f6V8rr3WABwwUahlEvr8y3IBniGKdKYBQ5OGQpukQakBpIVcfwptIhJcf8hWGakdndAAhBInIGHbdQGJg6jjbDUgEE5EpmB+AAM4uj6gb+AQT6wdhITLvAHJ4VCtgoAlG1tpNA0gWON/f4ioHdSADc1bfgt+PZFkDlD6ojWF+kVoaHlhvFjPHuVRrefohY1GdcFm1N8JvwEyrJ/X2Th2rIoVgIi3Fo6Xf0z5k8psKu5f/oi+nHjjI92o36AAAAABJRU5ErkJggg== ' ) ) ) ;
final iconFile = MemoryImage ( Uint8List . fromList ( base64Decode (
' iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAH+CAESEN8jyZkcIb5N/ONy3vmHhmiGjUm7UwS+YAAAHZSURBVGje7dnbboMwDIBhBwgQoFAO7Ta//4NOqCAXYZQstatq4r+r5ubrgQSpg8iyC4ZURa+PlIpQYGiwrzyeHtYZjAL8T05O4H8BbbKvFgRa4NoBU8pXeYEkDDgaaLQBcwJrmeErJQB/7wes3QBWGnCIX0+AQycL1PO6BMwPa0nA4ZxbgTvOjUYMGPHRnZkQAY4mxPZBjmy53E7ukSkFKYB/D4XsWZQx64sCeYebOogGsoOBYvv6/UCb8F0IOBZ0TlP6lEYdANY350AJqB9/qPVuOI5evw4A1hgLigAlepnyxW80bcCcwN++A2s82Vcu02ta+ceq9BoL5KGTTRwQPlpqA3gCnwWU2kCDgeWRQPj2jAPCDxgCMjhI6uZnToDpvd/BJeFrJQB/fsAa02gCt3mi1wNuy8GgBNDZlysBNNSrADVSjcJl6vCpUn6jOdx0kz0q6PMhQRa4465SFKhx35cgUCBTwj2/NHwZAb71qR8GEP2H1XcmAtBPTEO67GP6FUUAIKGABbDLQ0EArhN2sAIGesRO+iyy+RMAjckVTlMCKFVAbh/4Af9OPgG61SkDVco3BQGT3GXaDAnTIAcYZDuBTwGsAGDxuBFeAQqIqwoFMlAVLrHr/wId5MPt0nilGgAAAABJRU5ErkJggg== ' ) ) ) ;
2020-11-16 01:13:26 +08:00
class MyTheme {
2020-11-20 16:37:48 +08:00
MyTheme . _ ( ) ;
2022-02-02 17:25:56 +08:00
2020-11-16 01:13:26 +08:00
static const Color grayBg = Color ( 0xFFEEEEEE ) ;
static const Color white = Color ( 0xFFFFFFFF ) ;
2020-11-16 22:00:09 +08:00
static const Color accent = Color ( 0xFF0071FF ) ;
2020-11-19 17:22:42 +08:00
static const Color accent50 = Color ( 0x770071FF ) ;
2020-11-27 02:14:27 +08:00
static const Color accent80 = Color ( 0xAA0071FF ) ;
2020-11-19 18:41:37 +08:00
static const Color canvasColor = Color ( 0xFF212121 ) ;
2020-11-20 13:06:52 +08:00
static const Color border = Color ( 0xFFCCCCCC ) ;
2022-01-31 16:22:05 +08:00
static const Color idColor = Color ( 0xFF00B6F0 ) ;
static const Color darkGray = Color ( 0xFFB9BABC ) ;
2022-08-17 21:28:36 +08:00
static const Color cmIdColor = Color ( 0xFF21790B ) ;
2022-05-30 13:25:06 +08:00
static const Color dark = Colors . black87 ;
2022-08-19 15:44:19 +08:00
static const Color disabledTextLight = Color ( 0xFF888888 ) ;
static const Color disabledTextDark = Color ( 0xFF777777 ) ;
2022-07-29 16:47:24 +08:00
static ThemeData lightTheme = ThemeData (
brightness: Brightness . light ,
primarySwatch: Colors . blue ,
visualDensity: VisualDensity . adaptivePlatformDensity ,
tabBarTheme: TabBarTheme ( labelColor: Colors . black87 ) ,
) ;
static ThemeData darkTheme = ThemeData (
brightness: Brightness . dark ,
primarySwatch: Colors . blue ,
visualDensity: VisualDensity . adaptivePlatformDensity ,
tabBarTheme: TabBarTheme ( labelColor: Colors . white70 ) ) ;
}
bool isDarkTheme ( ) {
final isDark = " Y " = = Get . find < SharedPreferences > ( ) . getString ( " darkTheme " ) ;
return isDark ;
2020-11-16 01:13:26 +08:00
}
2021-08-02 20:54:56 +08:00
final ButtonStyle flatButtonStyle = TextButton . styleFrom (
2022-05-18 15:47:07 +08:00
minimumSize: Size ( 0 , 36 ) ,
padding: EdgeInsets . symmetric ( horizontal: 16.0 , vertical: 10.0 ) ,
2021-08-02 20:54:56 +08:00
shape: const RoundedRectangleBorder (
borderRadius: BorderRadius . all ( Radius . circular ( 2.0 ) ) ,
) ,
) ;
2022-08-18 00:34:04 +08:00
String formatDurationToTime ( Duration duration ) {
var totalTime = duration . inSeconds ;
final secs = totalTime % 60 ;
totalTime = ( totalTime - secs ) ~ / 60 ;
final mins = totalTime % 60 ;
totalTime = ( totalTime - mins ) ~ / 60 ;
return " ${ totalTime . toString ( ) . padLeft ( 2 , " 0 " ) } : ${ mins . toString ( ) . padLeft ( 2 , " 0 " ) } : ${ secs . toString ( ) . padLeft ( 2 , " 0 " ) } " ;
}
2022-08-16 21:27:21 +08:00
closeConnection ( { String ? id } ) {
2022-08-12 18:42:02 +08:00
if ( isAndroid | | isIOS ) {
Navigator . popUntil ( globalKey . currentContext ! , ModalRoute . withName ( " / " ) ) ;
} else {
2022-08-16 21:27:21 +08:00
closeTab ( id ) ;
2022-08-12 18:42:02 +08:00
}
2022-03-13 23:07:52 +08:00
}
2022-08-09 19:32:19 +08:00
void window_on_top ( int ? id ) {
if ( id = = null ) {
// main window
windowManager . restore ( ) ;
windowManager . show ( ) ;
windowManager . focus ( ) ;
} else {
2022-08-09 21:12:55 +08:00
WindowController . fromWindowId ( id )
. . focus ( )
. . show ( ) ;
2022-08-09 19:32:19 +08:00
}
2022-08-09 09:01:06 +08:00
}
2022-03-13 00:32:44 +08:00
typedef DialogBuilder = CustomAlertDialog Function (
2022-04-19 13:07:45 +08:00
StateSetter setState , void Function ( [ dynamic ] ) close ) ;
2022-03-13 00:32:44 +08:00
2022-08-12 18:42:02 +08:00
class Dialog < T > {
OverlayEntry ? entry ;
2022-08-15 19:31:58 +08:00
Completer < T ? > completer = Completer < T ? > ( ) ;
2022-08-12 18:42:02 +08:00
Dialog ( ) ;
void complete ( T ? res ) {
try {
if ( ! completer . isCompleted ) {
completer . complete ( res ) ;
}
} catch ( e ) {
debugPrint ( " Dialog complete catch error: $ e " ) ;
2022-08-15 19:31:58 +08:00
} finally {
entry ? . remove ( ) ;
2022-08-12 18:42:02 +08:00
}
}
}
class OverlayDialogManager {
OverlayState ? _overlayState ;
Map < String , Dialog > _dialogs = Map ( ) ;
int _tagCount = 0 ;
2022-03-13 00:32:44 +08:00
2022-08-12 18:42:02 +08:00
/// By default OverlayDialogManager use global overlay
OverlayDialogManager ( ) {
_overlayState = globalKey . currentState ? . overlay ;
2022-02-28 18:29:25 +08:00
}
2022-03-13 00:32:44 +08:00
2022-08-12 18:42:02 +08:00
void setOverlayState ( OverlayState ? overlayState ) {
_overlayState = overlayState ;
}
void dismissAll ( ) {
_dialogs . forEach ( ( key , value ) {
value . complete ( null ) ;
BackButtonInterceptor . removeByName ( key ) ;
} ) ;
_dialogs . clear ( ) ;
}
void dismissByTag ( String tag ) {
_dialogs [ tag ] ? . complete ( null ) ;
_dialogs . remove ( tag ) ;
BackButtonInterceptor . removeByName ( tag ) ;
}
Future < T ? > show < T > ( DialogBuilder builder ,
2022-04-20 23:43:19 +08:00
{ bool clickMaskDismiss = false ,
2022-08-03 22:03:31 +08:00
bool backDismiss = false ,
String ? tag ,
2022-08-12 18:42:02 +08:00
bool useAnimation = true ,
bool forceGlobal = false } ) {
final overlayState =
forceGlobal ? globalKey . currentState ? . overlay : _overlayState ;
if ( overlayState = = null ) {
return Future . error (
" [OverlayDialogManager] Failed to show dialog, _overlayState is null, call [setOverlayState] first " ) ;
}
final _tag ;
2022-04-19 13:07:45 +08:00
if ( tag ! = null ) {
2022-08-12 18:42:02 +08:00
_tag = tag ;
2022-04-19 13:07:45 +08:00
} else {
2022-08-12 18:42:02 +08:00
_tag = _tagCount . toString ( ) ;
_tagCount + + ;
2022-04-19 13:07:45 +08:00
}
2022-08-12 18:42:02 +08:00
final dialog = Dialog < T > ( ) ;
_dialogs [ _tag ] = dialog ;
2022-04-19 13:07:45 +08:00
final close = ( [ res ] ) {
2022-08-12 18:42:02 +08:00
_dialogs . remove ( _tag ) ;
dialog . complete ( res ) ;
BackButtonInterceptor . removeByName ( _tag ) ;
2022-04-19 13:07:45 +08:00
} ;
2022-08-12 18:42:02 +08:00
dialog . entry = OverlayEntry ( builder: ( _ ) {
2022-08-15 14:39:31 +08:00
bool innerClicked = false ;
return Listener (
onPointerUp: ( _ ) {
if ( ! innerClicked & & clickMaskDismiss ) {
close ( ) ;
}
innerClicked = false ;
} ,
child: Container (
color: Colors . black12 ,
child: StatefulBuilder ( builder: ( context , setState ) {
return Listener (
onPointerUp: ( _ ) = > innerClicked = true ,
child: builder ( setState , close ) ,
) ;
} ) ) ) ;
2022-08-12 18:42:02 +08:00
} ) ;
overlayState . insert ( dialog . entry ! ) ;
BackButtonInterceptor . add ( ( stopDefaultButtonEvent , routeInfo ) {
if ( backDismiss ) {
close ( ) ;
}
return true ;
} , name: _tag ) ;
return dialog . completer . future ;
}
void showLoading ( String text ,
2022-08-15 14:39:31 +08:00
{ bool clickMaskDismiss = false ,
bool showCancel = true ,
VoidCallback ? onCancel } ) {
2022-08-12 18:42:02 +08:00
show ( ( setState , close ) = > CustomAlertDialog (
content: Container (
constraints: BoxConstraints ( maxWidth: 240 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
SizedBox ( height: 30 ) ,
Center ( child: CircularProgressIndicator ( ) ) ,
SizedBox ( height: 20 ) ,
Center (
child: Text ( translate ( text ) ,
style: TextStyle ( fontSize: 15 ) ) ) ,
SizedBox ( height: 20 ) ,
2022-08-15 14:39:31 +08:00
Offstage (
offstage: ! showCancel ,
child: Center (
child: TextButton (
style: flatButtonStyle ,
onPressed: ( ) {
dismissAll ( ) ;
if ( onCancel ! = null ) {
onCancel ( ) ;
}
} ,
child: Text ( translate ( ' Cancel ' ) ,
style: TextStyle ( color: MyTheme . accent ) ) ) ) )
2022-08-12 18:42:02 +08:00
] ) ) ) ) ;
}
2022-08-15 14:39:31 +08:00
}
2022-08-12 18:42:02 +08:00
2022-08-15 14:39:31 +08:00
void showToast ( String text , { Duration timeout = const Duration ( seconds: 2 ) } ) {
final overlayState = globalKey . currentState ? . overlay ;
if ( overlayState = = null ) return ;
final entry = OverlayEntry ( builder: ( _ ) {
return IgnorePointer (
child: Align (
alignment: Alignment ( 0.0 , 0.8 ) ,
child: Container (
decoration: BoxDecoration (
color: Colors . black . withOpacity ( 0.6 ) ,
borderRadius: BorderRadius . all (
Radius . circular ( 20 ) ,
) ,
) ,
padding: EdgeInsets . symmetric ( horizontal: 20 , vertical: 5 ) ,
child: Text (
text ,
style: TextStyle (
decoration: TextDecoration . none ,
fontWeight: FontWeight . w300 ,
fontSize: 18 ,
color: Colors . white ) ,
) ,
) ) ) ;
} ) ;
overlayState . insert ( entry ) ;
Future . delayed ( timeout , ( ) {
entry . remove ( ) ;
} ) ;
2022-02-28 16:11:21 +08:00
}
2022-02-02 17:25:56 +08:00
2022-03-13 00:32:44 +08:00
class CustomAlertDialog extends StatelessWidget {
2022-08-03 22:03:31 +08:00
CustomAlertDialog (
2022-08-12 18:42:02 +08:00
{ this . title , required this . content , this . actions , this . contentPadding } ) ;
2020-11-18 12:49:43 +08:00
2022-08-12 18:42:02 +08:00
final Widget ? title ;
2022-03-13 00:32:44 +08:00
final Widget content ;
2022-08-12 18:42:02 +08:00
final List < Widget > ? actions ;
2022-03-13 00:32:44 +08:00
final double ? contentPadding ;
@ override
Widget build ( BuildContext context ) {
2022-04-21 10:02:47 +08:00
return AlertDialog (
scrollable: true ,
title: title ,
contentPadding:
2022-08-03 22:03:31 +08:00
EdgeInsets . symmetric ( horizontal: contentPadding ? ? 25 , vertical: 10 ) ,
2022-04-21 10:02:47 +08:00
content: content ,
actions: actions ,
) ;
2022-03-13 00:32:44 +08:00
}
2020-11-16 22:00:09 +08:00
}
2020-11-16 22:12:32 +08:00
2022-08-12 18:42:02 +08:00
void msgBox (
String type , String title , String text , OverlayDialogManager dialogManager ,
{ bool ? hasCancel } ) {
2020-11-29 01:36:10 +08:00
var wrap = ( String text , void Function ( ) onPressed ) = > ButtonTheme (
padding: EdgeInsets . symmetric ( horizontal: 20 , vertical: 10 ) ,
2022-02-02 17:25:56 +08:00
materialTapTargetSize: MaterialTapTargetSize . shrinkWrap ,
//limits the touch area to the button area
minWidth: 0 ,
//wraps child's width
2020-11-29 01:36:10 +08:00
height: 0 ,
2021-08-02 20:54:56 +08:00
child: TextButton (
style: flatButtonStyle ,
2020-11-29 01:36:10 +08:00
onPressed: onPressed ,
2022-08-12 18:42:02 +08:00
child:
Text ( translate ( text ) , style: TextStyle ( color: MyTheme . accent ) ) ) ) ;
2020-11-29 01:36:10 +08:00
2022-08-12 18:42:02 +08:00
dialogManager . dismissAll ( ) ;
2022-08-08 22:00:01 +08:00
List < Widget > buttons = [ ] ;
if ( type ! = " connecting " & & type ! = " success " & & type . indexOf ( " nook " ) < 0 ) {
buttons . insert (
0 ,
2022-08-12 18:42:02 +08:00
wrap ( translate ( ' OK ' ) , ( ) {
dialogManager . dismissAll ( ) ;
2022-08-16 21:27:21 +08:00
closeConnection ( ) ;
2022-08-08 22:00:01 +08:00
} ) ) ;
}
2020-11-24 11:25:56 +08:00
if ( hasCancel = = null ) {
2022-08-08 22:00:01 +08:00
// hasCancel = type != 'error';
hasCancel = type . indexOf ( " error " ) < 0 & &
type . indexOf ( " nocancel " ) < 0 & &
type ! = " restarting " ;
2020-11-24 11:25:56 +08:00
}
2020-11-29 01:36:10 +08:00
if ( hasCancel ) {
buttons . insert (
2022-04-19 13:07:45 +08:00
0 ,
2022-08-12 18:42:02 +08:00
wrap ( translate ( ' Cancel ' ) , ( ) {
dialogManager . dismissAll ( ) ;
2020-11-29 01:36:10 +08:00
} ) ) ;
}
2022-08-08 22:00:01 +08:00
// TODO: test this button
if ( type . indexOf ( " hasclose " ) > = 0 ) {
buttons . insert (
0 ,
2022-08-12 18:42:02 +08:00
wrap ( translate ( ' Close ' ) , ( ) {
dialogManager . dismissAll ( ) ;
2022-08-08 22:00:01 +08:00
} ) ) ;
}
2022-08-12 18:42:02 +08:00
dialogManager . show ( ( setState , close ) = > CustomAlertDialog (
2022-04-19 13:07:45 +08:00
title: Text ( translate ( title ) , style: TextStyle ( fontSize: 21 ) ) ,
2022-08-12 18:42:02 +08:00
content: Text ( translate ( text ) , style: TextStyle ( fontSize: 15 ) ) ,
2022-04-19 13:07:45 +08:00
actions: buttons ) ) ;
2020-11-21 14:40:28 +08:00
}
2020-11-25 18:33:09 +08:00
Color str2color ( String str , [ alpha = 0xFF ] ) {
var hash = 160 < < 16 + 114 < < 8 + 91 ;
for ( var i = 0 ; i < str . length ; i + = 1 ) {
hash = str . codeUnitAt ( i ) + ( ( hash < < 5 ) - hash ) ;
}
2021-08-14 14:14:01 +08:00
hash = hash % 16777216 ;
return Color ( ( hash & 0xFF7FFF ) | ( alpha < < 24 ) ) ;
2020-11-25 18:33:09 +08:00
}
2022-02-02 17:25:56 +08:00
2022-03-17 21:03:52 +08:00
const K = 1024 ;
const M = K * K ;
const G = M * K ;
String readableFileSize ( double size ) {
if ( size < K ) {
2022-07-11 10:30:45 +08:00
return size . toStringAsFixed ( 2 ) + " B " ;
2022-03-17 21:03:52 +08:00
} else if ( size < M ) {
return ( size / K ) . toStringAsFixed ( 2 ) + " KB " ;
} else if ( size < G ) {
return ( size / M ) . toStringAsFixed ( 2 ) + " MB " ;
} else {
return ( size / G ) . toStringAsFixed ( 2 ) + " GB " ;
}
}
2022-04-14 15:37:47 +08:00
/// Flutter can't not catch PointerMoveEvent when size is 1
/// This will happen in Android AccessibilityService Input
/// android can't init dispatching size yet ,see: https://stackoverflow.com/questions/59960451/android-accessibility-dispatchgesture-is-it-possible-to-specify-pressure-for-a
/// use this temporary solution until flutter or android fixes the bug
class AccessibilityListener extends StatelessWidget {
final Widget ? child ;
static final offset = 100 ;
AccessibilityListener ( { this . child } ) ;
@ override
Widget build ( BuildContext context ) {
return Listener (
onPointerDown: ( evt ) {
2022-05-26 18:25:16 +08:00
if ( evt . size = = 1 ) {
GestureBinding . instance . handlePointerEvent ( PointerAddedEvent (
2022-04-14 15:37:47 +08:00
pointer: evt . pointer + offset , position: evt . position ) ) ;
2022-05-26 18:25:16 +08:00
GestureBinding . instance . handlePointerEvent ( PointerDownEvent (
2022-04-14 15:37:47 +08:00
pointer: evt . pointer + offset ,
size: 0.1 ,
position: evt . position ) ) ;
}
} ,
onPointerUp: ( evt ) {
2022-06-19 17:15:37 +01:00
if ( evt . size = = 1 ) {
2022-05-26 18:25:16 +08:00
GestureBinding . instance . handlePointerEvent ( PointerUpEvent (
2022-04-14 15:37:47 +08:00
pointer: evt . pointer + offset ,
size: 0.1 ,
position: evt . position ) ) ;
2022-05-26 18:25:16 +08:00
GestureBinding . instance . handlePointerEvent ( PointerRemovedEvent (
2022-04-15 17:45:48 +08:00
pointer: evt . pointer + offset , position: evt . position ) ) ;
2022-04-14 15:37:47 +08:00
}
} ,
onPointerMove: ( evt ) {
2022-06-19 17:15:37 +01:00
if ( evt . size = = 1 ) {
2022-05-26 18:25:16 +08:00
GestureBinding . instance . handlePointerEvent ( PointerMoveEvent (
2022-04-14 15:37:47 +08:00
pointer: evt . pointer + offset ,
size: 0.1 ,
delta: evt . delta ,
position: evt . position ) ) ;
}
} ,
child: child ) ;
}
}
2022-04-20 22:37:47 +08:00
class PermissionManager {
static Completer < bool > ? _completer ;
static Timer ? _timer ;
static var _current = " " ;
2022-07-16 22:31:44 +08:00
static final permissions = [
" audio " ,
" file " ,
" ignore_battery_optimizations " ,
" application_details_settings "
] ;
2022-04-20 22:37:47 +08:00
static bool isWaitingFile ( ) {
if ( _completer ! = null ) {
return ! _completer ! . isCompleted & & _current = = " file " ;
}
return false ;
}
static Future < bool > check ( String type ) {
2022-08-18 00:34:04 +08:00
if ( isDesktop ) {
return Future . value ( true ) ;
}
2022-04-20 22:37:47 +08:00
if ( ! permissions . contains ( type ) )
return Future . error ( " Wrong permission! $ type " ) ;
2022-06-13 21:07:26 +08:00
return gFFI . invokeMethod ( " check_permission " , type ) ;
2022-04-20 22:37:47 +08:00
}
static Future < bool > request ( String type ) {
2022-08-18 00:34:04 +08:00
if ( isDesktop ) {
return Future . value ( true ) ;
}
2022-04-20 22:37:47 +08:00
if ( ! permissions . contains ( type ) )
return Future . error ( " Wrong permission! $ type " ) ;
2022-08-01 14:33:08 +08:00
gFFI . invokeMethod ( " request_permission " , type ) ;
2022-07-14 17:44:37 +08:00
if ( type = = " ignore_battery_optimizations " ) {
return Future . value ( false ) ;
}
2022-04-20 22:37:47 +08:00
_current = type ;
_completer = Completer < bool > ( ) ;
2022-06-13 21:07:26 +08:00
gFFI . invokeMethod ( " request_permission " , type ) ;
2022-04-20 22:37:47 +08:00
// timeout
_timer ? . cancel ( ) ;
_timer = Timer ( Duration ( seconds: 60 ) , ( ) {
if ( _completer = = null ) return ;
if ( ! _completer ! . isCompleted ) {
_completer ! . complete ( false ) ;
}
_completer = null ;
_current = " " ;
} ) ;
return _completer ! . future ;
}
static complete ( String type , bool res ) {
if ( type ! = _current ) {
res = false ;
}
_timer ? . cancel ( ) ;
_completer ? . complete ( res ) ;
_current = " " ;
}
}
2022-06-13 21:07:26 +08:00
2022-07-29 18:34:25 +08:00
RadioListTile < T > getRadio < T > (
String name , T toValue , T curValue , void Function ( T ? ) onChange ) {
return RadioListTile < T > (
controlAffinity: ListTileControlAffinity . trailing ,
title: Text ( translate ( name ) ) ,
value: toValue ,
groupValue: curValue ,
onChanged: onChange ,
dense: true ,
) ;
}
2022-08-01 10:44:05 +08:00
2022-08-05 20:29:43 +08:00
CheckboxListTile getToggle (
2022-08-15 20:26:20 +08:00
String id , void Function ( void Function ( ) ) setState , option , name ,
{ FFI ? ffi } ) {
2022-08-16 15:22:57 +08:00
final opt = bind . sessionGetToggleOptionSync ( id: id , arg: option ) ;
2022-08-05 20:29:43 +08:00
return CheckboxListTile (
value: opt ,
onChanged: ( v ) {
setState ( ( ) {
bind . sessionToggleOption ( id: id , value: option ) ;
} ) ;
if ( option = = " show-quality-monitor " ) {
2022-08-15 20:26:20 +08:00
( ffi ? ? gFFI ) . qualityMonitorModel . checkShowQualityMonitor ( id ) ;
2022-08-05 20:29:43 +08:00
}
} ,
dense: true ,
title: Text ( translate ( name ) ) ) ;
}
2022-06-13 21:07:26 +08:00
/// find ffi, tag is Remote ID
/// for session specific usage
FFI ffi ( String ? tag ) {
return Get . find < FFI > ( tag: tag ) ;
}
/// Global FFI object
late FFI _globalFFI ;
FFI get gFFI = > _globalFFI ;
Future < void > initGlobalFFI ( ) async {
2022-08-04 17:24:02 +08:00
debugPrint ( " _globalFFI init " ) ;
2022-06-13 21:07:26 +08:00
_globalFFI = FFI ( ) ;
2022-08-04 17:24:02 +08:00
debugPrint ( " _globalFFI init end " ) ;
2022-06-13 21:07:26 +08:00
// after `put`, can also be globally found by Get.find<FFI>();
Get . put ( _globalFFI , permanent: true ) ;
2022-07-25 16:23:45 +08:00
// trigger connection status updater
2022-08-03 22:03:31 +08:00
await bind . mainCheckConnectStatus ( ) ;
2022-07-29 16:47:24 +08:00
// global shared preference
await Get . putAsync ( ( ) = > SharedPreferences . getInstance ( ) ) ;
2022-08-03 22:03:31 +08:00
}
2022-08-08 17:53:51 +08:00
String translate ( String name ) {
if ( name . startsWith ( ' Failed to ' ) & & name . contains ( ' : ' ) ) {
return name . split ( ' : ' ) . map ( ( x ) = > translate ( x ) ) . join ( ' : ' ) ;
}
return platformFFI . translate ( name , localeName ) ;
}
2022-08-13 12:43:35 +08:00
2022-08-15 11:08:42 +08:00
bool option2bool ( String option , String value ) {
2022-08-13 12:43:35 +08:00
bool res ;
2022-08-15 11:08:42 +08:00
if ( option . startsWith ( " enable- " ) ) {
2022-08-13 12:43:35 +08:00
res = value ! = " N " ;
2022-08-15 11:08:42 +08:00
} else if ( option . startsWith ( " allow- " ) | |
option = = " stop-service " | |
option = = " direct-server " | |
option = = " stop-rendezvous-service " ) {
2022-08-13 12:43:35 +08:00
res = value = = " Y " ;
} else {
assert ( false ) ;
res = value ! = " N " ;
}
return res ;
}
2022-08-15 11:08:42 +08:00
String bool2option ( String option , bool b ) {
2022-08-13 12:43:35 +08:00
String res ;
2022-08-15 11:08:42 +08:00
if ( option . startsWith ( ' enable- ' ) ) {
res = b ? ' ' : ' N ' ;
} else if ( option . startsWith ( ' allow- ' ) | |
option = = " stop-service " | |
option = = " direct-server " | |
option = = " stop-rendezvous-service " ) {
res = b ? ' Y ' : ' ' ;
2022-08-13 12:43:35 +08:00
} else {
assert ( false ) ;
2022-08-15 11:08:42 +08:00
res = b ? ' Y ' : ' N ' ;
2022-08-13 12:43:35 +08:00
}
return res ;
}