This commit is contained in:
2021-01-23 19:28:26 -05:00
parent fec7db1890
commit 4378a2927b
51 changed files with 992 additions and 245 deletions

View File

@@ -1,68 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:imagini/bloc/bloc-prov.dart';
/// A Flutter [Widget] that merges multiple [BlocProvider] widgets into one widget tree.
///
/// [BlocProviderTree] improves the readability and eliminates the need
/// to nest multiple [BlocProviders].
///
/// By using [BlocProviderTree] we can go from:
///
/// ```dart
/// BlocProvider<BlocA>(
/// bloc: BlocA(),
/// child: BlocProvider<BlocB>(
/// bloc: BlocB(),
/// child: BlocProvider<BlocC>(
/// value: BlocC(),
/// child: ChildA(),
/// )
/// )
/// )
/// ```
///
/// to:
///
/// ```dart
/// BlocProviderTree(
/// blocProviders: [
/// BlocProvider<BlocA>(bloc: BlocA()),
/// BlocProvider<BlocB>(bloc: BlocB()),
/// BlocProvider<BlocC>(bloc: BlocC()),
/// ],
/// child: ChildA(),
/// )
/// ```
///
/// [BlocProviderTree] converts the [BlocProvider] list
/// into a tree of nested [BlocProvider] widgets.
/// As a result, the only advantage of using [BlocProviderTree] is improved
/// readability due to the reduction in nesting and boilerplate.
class BlocProviderTree extends StatelessWidget {
/// The [BlocProvider] list which is converted into a tree of [BlocProvider] widgets.
/// The tree of [BlocProvider] widgets is created in order meaning the first [BlocProvider]
/// will be the top-most [BlocProvider] and the last [BlocProvider] will be a direct ancestor
/// of the `child` [Widget].
final List<BlocProvider> blocProviders;
/// The [Widget] and its descendants which will have access to every [Bloc] provided by `blocProviders`.
/// This [Widget] will be a direct descendent of the last [BlocProvider] in `blocProviders`.
final Widget child;
const BlocProviderTree({
Key key,
@required this.blocProviders,
@required this.child,
}) : assert(blocProviders != null),
assert(child != null),
super(key: key);
@override
Widget build(BuildContext context) {
Widget tree = child;
for (final blocProvider in blocProviders.reversed) {
tree = blocProvider.copyWith(tree);
}
return tree;
}
}

View File

@@ -1,60 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:imagini/bloc/bloc.dart';
/// A Flutter widget which provides a bloc to its children via `BlocProvider.of(context)`.
/// It is used as a DI widget so that a single instance of a bloc can be provided
/// to multiple widgets within a subtree.
class BlocProvider<T extends Bloc> extends InheritedWidget {
/// The [Bloc] which is to be made available throughout the subtree
final T bloc;
/// The [Widget] and its descendants which will have access to the [Bloc].
final Widget child;
BlocProvider({
Key key,
@required this.bloc,
this.child,
}) : assert(bloc != null),
super(key: key, child: child);
/// Method that allows widgets to access the bloc as long as their `BuildContext`
/// contains a `BlocProvider` instance.
static T of<T extends Bloc>(BuildContext context) {
final type = _typeOf<BlocProvider<T>>();
final BlocProvider<T> provider = context
.ancestorInheritedElementForWidgetOfExactType(type)
?.widget as BlocProvider<T>;
if (provider == null) {
throw FlutterError(
"""
BlocProvider.of() called with a context that does not contain a Bloc of type $T.
No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>().
This can happen if the context you use comes from a widget above the BlocProvider.
This can also happen if you used BlocProviderTree and didn\'t explicity provide
the BlocProvider types: BlocProvider(bloc: $T()) instead of BlocProvider<$T>(bloc: $T()).
The context used was: $context
""",
);
}
return provider?.bloc;
}
/// Clone the current [BlocProvider] with a new child [Widget].
/// All other values, including [Key] and [Bloc] are preserved.
BlocProvider<T> copyWith(Widget child) {
return BlocProvider<T>(
key: key,
bloc: bloc,
child: child,
);
}
/// Necessary to obtain generic [Type]
/// https://github.com/dart-lang/sdk/issues/11923
static Type _typeOf<T>() => T;
@override
bool updateShouldNotify(BlocProvider oldWidget) => false;
}

View File

@@ -1 +0,0 @@
abstract class Bloc {}

View File

@@ -1,5 +0,0 @@
import 'package:imagini/bloc/bloc.dart';
class AuthBloc extends Bloc {
AuthBloc();
}

View File

@@ -1,2 +0,0 @@
export 'auth-bloc.dart';
export 'pref-bloc.dart';

View File

@@ -0,0 +1,42 @@
import 'dart:io';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:imagini/blocs/login/events.dart';
import 'package:imagini/blocs/login/states.dart';
import 'package:imagini/services/api/api.dart';
import 'package:imagini/services/api/exceptions.dart';
class LoginBloc extends Bloc<LoginEvents, LoginState> {
final ImaginiAPI imaginiAPI;
Map<String, String> loginResult;
String exampleResult;
LoginBloc({ this.imaginiAPI }) : super(LoginInitState());
@override
Stream<LoginState> mapEventToState(LoginEvents event) async* {
switch (event) {
case LoginEvents.loginResult:
yield LoginLoading();
try {
// exampleResult = await imaginiAPI.exampleApi();
loginResult = await imaginiAPI.loginAPI("admin", "admin");
yield LoginSuccess();
} on SocketException {
yield LoginFailed(
error: ConnectionRefusedException('No Internet'),
);
} on FormatException {
yield LoginFailed(
error: InvalidFormatException('Invalid Response Format'),
);
} catch (e) {
print(e);
yield LoginFailed(
error: UnknownException('Unknown Error'),
);
}
break;
}
}
}

View File

@@ -0,0 +1,3 @@
enum LoginEvents {
loginResult,
}

View File

@@ -0,0 +1,28 @@
// import 'package:equatable/equatable.dart';
//
// abstract class LoginState extends Equatable {
// @override
// List<Object> get props => [];
// }
abstract class LoginState {}
class LoginInitState extends LoginState {}
class LoginLoading extends LoginState {}
class LoginSuccess extends LoginState {}
class LoginNeeded extends LoginState {}
class LoginFailed extends LoginState {
final error;
LoginFailed({this.error});
}
class LoginLoaded extends LoginState {}
class LoginListError extends LoginState {
final error;
LoginListError({this.error});
}

View File

@@ -1,5 +0,0 @@
import 'package:imagini/bloc/bloc.dart';
class PrefBloc extends Bloc {
PrefBloc();
}

View File

@@ -0,0 +1,23 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:imagini/blocs/theme/events.dart';
import 'package:imagini/blocs/theme/state.dart';
import 'package:imagini/settings/app_themes.dart';
class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
//
ThemeBloc()
: super(
ThemeState(
themeData: AppThemes.appThemeData[AppTheme.lightTheme],
),
);
@override
Stream<ThemeState> mapEventToState(ThemeEvent event) async* {
if (event is ThemeEvent) {
yield ThemeState(
themeData: AppThemes.appThemeData[event.appTheme],
);
}
}
}

View File

@@ -0,0 +1,6 @@
import 'package:imagini/settings/app_themes.dart';
class ThemeEvent {
final AppTheme appTheme;
ThemeEvent({this.appTheme});
}

View File

@@ -0,0 +1,6 @@
import 'package:flutter/material.dart';
class ThemeState {
final ThemeData themeData;
ThemeState({this.themeData});
}

View File

@@ -1,27 +1,37 @@
import 'package:flutter/material.dart';
import 'package:imagini/theme/style.dart';
import 'package:imagini/routes.dart';
import 'package:imagini/bloc/bloc-prov-tree.dart';
import 'package:imagini/bloc/bloc-prov.dart';
import 'package:imagini/blocs/blocs.dart';
import 'blocs/blocs.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(ExampleApp());
import 'package:imagini/services/api/api.dart';
import 'package:imagini/screens/login.dart';
import 'package:imagini/blocs/login/bloc.dart';
import 'package:imagini/settings/preferences.dart';
import 'package:imagini/blocs/theme/bloc.dart';
import 'package:imagini/blocs/theme/state.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Preferences.init();
runApp(ImaginiApp());
}
class ExampleApp extends StatelessWidget {
class ImaginiApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProviderTree(
blocProviders: <BlocProvider>[
BlocProvider<AuthBloc>(bloc: AuthBloc()),
BlocProvider<PrefBloc>(bloc: PrefBloc()),
],
child: MaterialApp(
title: 'ExampleApp',
theme: appTheme(),
initialRoute: '/',
routes: routes,
return BlocProvider(
create: (context) => ThemeBloc(),
child: BlocBuilder<ThemeBloc, ThemeState>(builder: (BuildContext context, ThemeState themeState) {
return MaterialApp(
title: 'Imagini',
debugShowCheckedModeBanner: false,
theme: themeState.themeData,
home: BlocProvider(
// create: (context) => LoginBloc(albumsRepo: AlbumServices()),
create: (context) => LoginBloc(imaginiAPI: ImaginiAPI()),
child: LoginScreen(),
),
);
},
),
);
}

View File

@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:imagini/blocs/login/bloc.dart';
import 'package:imagini/blocs/login/states.dart';
import 'package:imagini/blocs/login/events.dart';
import 'package:imagini/blocs/theme/bloc.dart';
import 'package:imagini/blocs/theme/events.dart';
import 'package:imagini/settings/preferences.dart';
import 'package:imagini/settings/app_themes.dart';
class LoginScreen extends StatefulWidget {
@override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
@override
void initState() {
super.initState();
_loadTheme();
_loadLogin();
}
_loadTheme() async {
context.read<ThemeBloc>().add(ThemeEvent(appTheme: Preferences.getTheme()));
}
_loadLogin() async {
context.read<LoginBloc>().add(LoginEvents.loginResult);
}
_setTheme(bool darkTheme) async {
AppTheme selectedTheme =
darkTheme ? AppTheme.lightTheme : AppTheme.darkTheme;
context.read<ThemeBloc>().add(ThemeEvent(appTheme: selectedTheme));
Preferences.saveTheme(selectedTheme);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _body(),
);
}
_body() {
return BlocBuilder<LoginBloc, LoginState>(builder: (BuildContext context, LoginState state) {
// Set Theme
_setTheme(true);
if (state is LoginNeeded) {
// TODO: Load Login Form
return Center( child: Text("Login Needed") );
}
if (state is LoginFailed) {
// TODO: Update Form Failed
return Center( child: Text("Login Failed: ${state.error.message.toString()}") );
}
if (state is LoginSuccess) {
// TODO: Navigate to /Gallery
return Center( child: Text("Login Success") );
}
// TODO: Login Screen
return Center( child: Text("Login Loading") );
});
}
}

View File

@@ -1,16 +0,0 @@
import 'package:flutter/material.dart';
class Body extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
onPressed: () {
// Go to Login Screen
Navigator.pushNamed(context, '/Gallery');
},
child: Text('Login'),
),
);
}
}

View File

@@ -31,7 +31,7 @@ class _GalleryScreenState extends State<GalleryScreen> {
bloc: exampleBloc,
child: PlatformScaffold(
appBar: PlatformAppBar(
title: Text('Gallerys'),
title: Text('Gallery'),
cupertino: (_, __) => CupertinoNavigationBarData(
// Issue with cupertino where a bar with no transparency
// will push the list down. Adding some alpha value fixes it (in a hacky way)

View File

@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
class Body extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0),
height: 500,
child: Column(
children: <Widget>[
Container(
child: FittedBox(
fit: BoxFit.contain,
child: const FlutterLogo(),
),
width: 175,
margin: EdgeInsets.fromLTRB(0, 0, 0, 50),
),
Expanded(
child: TextField(
decoration: InputDecoration(
labelText: 'Server Address'
),
),
),
Expanded(
child: TextField(
decoration: InputDecoration(
labelText: 'Username / Email'
),
),
),
Expanded(
child: TextField(
obscureText: true,
enableSuggestions: false,
autocorrect: false,
decoration: InputDecoration(
labelText: 'Password'
),
),
),
SizedBox(
width: double.infinity,
height: 50,
child: RaisedButton(
onPressed: () {
Navigator.pushNamed(context, '/Gallery');
},
child: Text('Login')
),
),
],
),
),
);
}
}

View File

@@ -28,9 +28,9 @@ class _LoginScreenState extends State<LoginScreen> {
return BlocProvider(
bloc: LoginBloc(),
child: Scaffold(
appBar: AppBar(
title: Text("Login"),
),
// appBar: AppBar(
// title: Text("Login"),
// ),
body: Body(),
),
);

View File

@@ -1,38 +0,0 @@
import 'package:flutter/material.dart';
import 'package:imagini/screens/splash/components/body.dart';
import 'package:imagini/screens/splash/splash-bloc.dart';
import 'package:imagini/bloc/bloc-prov.dart';
class SplashScreen extends StatefulWidget {
@override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
SplashBloc splashBloc;
@override
void initState() {
super.initState();
splashBloc = SplashBloc();
}
@override
void dispose() {
splashBloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
bloc: SplashBloc(),
child: Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: Body(),
),
);
}
}

View File

@@ -0,0 +1,85 @@
import 'dart:io';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
// import 'package:flutter/foundation.dart' show kIsWeb;
// import 'package:flutter_secure_storage/flutter_secure_storage.dart';
// if(kIsWeb) {
// // Use web storage
// } else {
// // Use flutter_secure_storage
// }
// final _storage = FlutterSecureStorage();
class ImaginiAPI {
static const _baseUrl = "http://10.0.20.170:8484";
// static const String _GET_ALBUMS = '/albums';
Future<String> exampleApi() async {
http.Response response = await http.get(
Uri.encodeFull("https://www.example.com/api"),
);
print("Status: ${response.statusCode.toString()}");
print("Respone: ${response.body.toString()}");
return response.body;
}
Future<Map<String, String>> loginAPI(String user, String password) async {
http.Response response = await http.post(
Uri.encodeFull(_baseUrl + "/api/v1/Login"),
body: jsonEncode(<String, String>{
'user': user,
'password': password,
}),
).timeout(Duration(seconds: 10));
// TODO:
// - StatusCode:
// - 405 (StatusMethodNotAllowed)
// - 400 (StatusBadRequest)
// - 401 (StatusUnauthorized)
// -
// -
// -
String setCookieVal = response.headers["set-cookie"];
List<Cookie> allCookies = setCookieVal.split(',')
.map((cookie) => Cookie.fromSetCookieValue(cookie)).toList();
Cookie accessToken = allCookies.firstWhere((cookie) => cookie.name == "AccessToken");
Cookie refreshToken = allCookies.firstWhere((cookie) => cookie.name == "RefreshToken");
print("Status: ${response.statusCode.toString()}");
print("Body: ${response.body.toString()}");
print("AccessToken: ${accessToken.toString()}");
print("RefreshToken: ${refreshToken.toString()}");
return response.headers;
}
Future<String> mediaItemsAPI(String albumID, String tagID) async {
return null;
}
Future<String> tagsAPI() async {
return null;
}
Future<String> albumsAPI() async {
return null;
}
Future<String> meAPI() async {
return null;
}
// API Calls:
// - Login
// - MediaItems
// - Tags
// - Albums
// - Me
}

View File

@@ -0,0 +1,19 @@
class ConnectionRefusedException {
var message;
ConnectionRefusedException(this.message);
}
class NoServiceFoundException {
var message;
NoServiceFoundException(this.message);
}
class InvalidFormatException {
var message;
InvalidFormatException(this.message);
}
class UnknownException {
var message;
UnknownException(this.message);
}

View File

@@ -1,11 +0,0 @@
import 'dart:async';
import 'package:http/http.dart' as http;
Future<String> exampleApi(String orgid) async {
http.Response response = await http.get(
Uri.encodeFull("https://www.example.com/api"),
);
print("Respone ${response.body.toString()}");
//Returns 'true' or 'false' as a String
return response.body;
}

View File

@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
class AppThemes {
static final appThemeData = {
AppTheme.lightTheme: ThemeData(
scaffoldBackgroundColor: Colors.white,
primarySwatch: Colors.blue,
backgroundColor: Colors.white,
textTheme: TextTheme(
bodyText1: TextStyle(
color: Colors.black,
),
),
),
AppTheme.darkTheme: ThemeData(
scaffoldBackgroundColor: Colors.black,
primarySwatch: Colors.teal,
backgroundColor: Colors.black,
textTheme: TextTheme(
bodyText1: TextStyle(
color: Colors.white,
),
),
)
};
}
enum AppTheme {
lightTheme,
darkTheme,
}

View File

@@ -0,0 +1,39 @@
import 'dart:convert';
import 'package:imagini/settings/app_themes.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Preferences {
//
static SharedPreferences preferences;
static const String KEY_SELECTED_THEME = 'key_selected_theme';
static init() async {
preferences = await SharedPreferences.getInstance();
}
static void saveTheme(AppTheme selectedTheme) async {
if (null == selectedTheme) {
selectedTheme = AppTheme.lightTheme;
}
String theme = jsonEncode(selectedTheme.toString());
preferences.setString(KEY_SELECTED_THEME, theme);
}
static AppTheme getTheme() {
String theme = preferences.getString(KEY_SELECTED_THEME);
if (null == theme) {
return AppTheme.lightTheme;
}
return getThemeFromString(jsonDecode(theme));
}
static AppTheme getThemeFromString(String themeString) {
for (AppTheme theme in AppTheme.values) {
if (theme.toString() == themeString) {
return theme;
}
}
return null;
}
}