Added Auto-Login
This commit is contained in:
parent
29a89b951c
commit
e59db71810
8 changed files with 290 additions and 23 deletions
|
@ -1,8 +1,10 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:gitea_client/cubit/repo_event.dart';
|
||||
import 'package:gitea_client/model/repository.dart';
|
||||
import 'package:gitea_client/service/gitea_service.dart';
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
|
||||
part 'repo_state.dart';
|
||||
|
||||
|
@ -12,7 +14,8 @@ class RepoCubit extends Cubit<RepoState> {
|
|||
|
||||
class RepoBloc extends Bloc<RepoEvent, RepoState> {
|
||||
RepoBloc({required this.giteaService}) : super(const RepoState()) {
|
||||
on<RepoFetched>(_onRepoFetched);
|
||||
on<RepoFetched>(_onRepoFetched,
|
||||
transformer: (events, mapper) => events.switchMap(mapper),);
|
||||
}
|
||||
|
||||
final GiteaService giteaService;
|
||||
|
@ -31,11 +34,17 @@ class RepoBloc extends Bloc<RepoEvent, RepoState> {
|
|||
));
|
||||
}
|
||||
final repos = await giteaService.getUserRepositories(state.loadedPages+1,100);
|
||||
final repoList = List.of(state.repos);
|
||||
repos.forEach((element) {
|
||||
if(repoList.where((selement) => selement.id == element.id).isEmpty) {
|
||||
repoList.add(element);
|
||||
}
|
||||
});
|
||||
emit(repos.isEmpty
|
||||
? state.copyWith(hasReachedMax: true)
|
||||
: state.copyWith(
|
||||
status: RepoStatus.success,
|
||||
repos: List.of(state.repos)..addAll(repos),
|
||||
repos: repoList,
|
||||
loadedPages: state.loadedPages+1,
|
||||
hasReachedMax: false,
|
||||
error_message: null,
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:gitea_client/model/user.dart';
|
|||
import 'package:gitea_client/widget/login_form.dart';
|
||||
import 'package:gitea_client/widget/login_status.dart';
|
||||
import 'package:gitea_client/widget/repo_list_page.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'model/ApiAccess.dart';
|
||||
|
||||
|
@ -21,7 +22,7 @@ class MyApp extends StatelessWidget {
|
|||
theme: ThemeData(
|
||||
primarySwatch: Colors.lightGreen,
|
||||
),
|
||||
home: const LoginPage(title: 'Gitea client'),
|
||||
home: MyHomePage(),
|
||||
routes: {
|
||||
|
||||
},
|
||||
|
@ -47,6 +48,47 @@ class MyApp extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
@override
|
||||
_MyHomePage createState() => _MyHomePage();
|
||||
}
|
||||
|
||||
class _MyHomePage extends State<MyHomePage> {
|
||||
|
||||
late SharedPreferences prefs;
|
||||
ApiAccess? _apiAccess;
|
||||
var loggedIn = false;
|
||||
|
||||
|
||||
void _autoLogin() async {
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
final token = prefs.getString("token");
|
||||
final instance = prefs.getString("instance");
|
||||
if(token != null && instance != null) {
|
||||
await prefs.setBool("autoLogin", true);
|
||||
setState(() {
|
||||
loggedIn = true;
|
||||
_apiAccess = ApiAccess(instance, token);
|
||||
});
|
||||
|
||||
print("Auto login");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_autoLogin();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return (loggedIn) ? StatefulLoginStatus(apiAccess: _apiAccess!) : const LoginPage(title: "Login to Gitea");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class LoginPage extends StatelessWidget {
|
||||
const LoginPage({Key? key, required this.title}) : super(key: key);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../model/ApiAccess.dart';
|
||||
|
@ -17,6 +18,14 @@ class _LoginForm extends State<StatefulLoginForm> {
|
|||
final instanceController = TextEditingController();
|
||||
String instance = "gitea.com";
|
||||
|
||||
late SharedPreferences prefs;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final media = MediaQuery.of(context).size;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../model/ApiAccess.dart';
|
||||
import '../model/user.dart';
|
||||
|
@ -19,11 +20,15 @@ class _StatefulLoginStatus extends State<StatefulLoginStatus> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
_initPrefs();
|
||||
userRequest =
|
||||
AuthenticationChecker(widget.apiAccess).getAuthenticatedUserOrError();
|
||||
_autoLogin();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
late SharedPreferences prefs;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -79,9 +84,12 @@ class _StatefulLoginStatus extends State<StatefulLoginStatus> {
|
|||
)),
|
||||
ElevatedButton(
|
||||
onPressed: () => {
|
||||
Navigator.pushNamed(context, "/repolist",arguments:
|
||||
SavedUser(authedUser: user, apiAccess: widget.apiAccess))
|
||||
},
|
||||
_saveApiAccess(widget.apiAccess),
|
||||
Navigator.pushNamed(context, "/repolist",
|
||||
arguments: SavedUser(
|
||||
authedUser: user,
|
||||
apiAccess: widget.apiAccess))
|
||||
},
|
||||
child: const Text("Start using Gitea"))
|
||||
],
|
||||
));
|
||||
|
@ -95,4 +103,27 @@ class _StatefulLoginStatus extends State<StatefulLoginStatus> {
|
|||
),
|
||||
));
|
||||
}
|
||||
|
||||
void _initPrefs() async {
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
void _saveApiAccess(ApiAccess apiAccess) async {
|
||||
await prefs.setString('instance', apiAccess.instance);
|
||||
await prefs.setString('token', apiAccess.token);
|
||||
}
|
||||
|
||||
void _autoLogin() async{
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
bool? autologin = prefs.getBool("autoLogin");
|
||||
if(autologin != null){
|
||||
userRequest!.then((user) => {
|
||||
Navigator.pushNamed(context, "/repolist",
|
||||
arguments: SavedUser(
|
||||
authedUser: user,
|
||||
apiAccess: widget.apiAccess))
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,13 +29,16 @@ class _ReposListState extends State<ReposList> {
|
|||
return Center(child: Text('failed to fetch $error_message'));
|
||||
case RepoStatus.success:
|
||||
if (state.repos.isEmpty) {
|
||||
return const Center(child: Text('no posts'));
|
||||
return const Center(child: Text('no repos'));
|
||||
}
|
||||
if(state.repos.length < 5) {
|
||||
context.read<RepoBloc>().add(RepoFetched());
|
||||
}
|
||||
return ListView.builder(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return index >= state.repos.length
|
||||
? BottomLoader()
|
||||
: RepoListItem(post: state.repos[index]);
|
||||
: RepoListItem(repo: state.repos[index]);
|
||||
},
|
||||
itemCount: state.hasReachedMax
|
||||
? state.repos.length
|
||||
|
@ -83,20 +86,34 @@ class BottomLoader extends StatelessWidget {
|
|||
}
|
||||
|
||||
class RepoListItem extends StatelessWidget {
|
||||
const RepoListItem({Key? key, required this.post}) : super(key: key);
|
||||
const RepoListItem({Key? key, required this.repo}) : super(key: key);
|
||||
|
||||
final Repository post;
|
||||
final Repository repo;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Material(
|
||||
child: ListTile(
|
||||
leading: Text('${post.updatedAt!.toString()}', style: textTheme.caption),
|
||||
title: Text(post.name),
|
||||
isThreeLine: true,
|
||||
subtitle: Text(post.owner.username!),
|
||||
dense: true,
|
||||
child: Container(
|
||||
color: (repo.private!) ? Colors.yellow[100] : Colors.white,
|
||||
child: ListTile(
|
||||
leading: (repo.mirror!) ? const Icon(Icons.amp_stories_outlined) : (repo.archived!) ? const Icon(Icons.archive) : const Icon(Icons.book) ,
|
||||
title: Text('${repo.owner.username}/${repo.name}'),
|
||||
isThreeLine: true,
|
||||
subtitle: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
(repo.description != null) ? Text(repo.description!) : Container(),
|
||||
(repo.mirror!) ? Text("Mirror of ${repo.originalUrl!}") : Container()
|
||||
],
|
||||
),
|
||||
dense: true,
|
||||
onTap: () => Scaffold
|
||||
.of(context)
|
||||
.showSnackBar(SnackBar(content: Text(repo.fullName!.toString()))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,26 +9,64 @@ import 'package:gitea_client/widget/repo_list.dart';
|
|||
class RepoListPage extends StatefulWidget {
|
||||
final SavedUser savedUser;
|
||||
|
||||
const RepoListPage({Key? key,required this.savedUser}) : super (key: key);
|
||||
const RepoListPage({Key? key, required this.savedUser}) : super(key: key);
|
||||
|
||||
@override
|
||||
_RepoListPage createState() => _RepoListPage();
|
||||
|
||||
}
|
||||
|
||||
class _RepoListPage extends State<RepoListPage> {
|
||||
|
||||
final GlobalKey<ScaffoldState> _key = GlobalKey();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _key,
|
||||
appBar: AppBar(
|
||||
title: const Text("Repositories"),
|
||||
leading: IconButton(icon: const Icon(Icons.menu),onPressed: ()=> {},),
|
||||
title: const Text("My Repositories"),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: () => _key.currentState!.openDrawer(),
|
||||
),
|
||||
),
|
||||
drawer: Drawer(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
DrawerHeader(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
),
|
||||
child: Row(
|
||||
|
||||
children: [
|
||||
Container(padding: EdgeInsets.all(5),child: Image.network(widget.savedUser.authedUser.avatarUrl!,width: 60,)),
|
||||
Text(
|
||||
widget.savedUser.authedUser.username!,
|
||||
style:
|
||||
TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
color: Colors.lightGreen[100],
|
||||
child: const ListTile(
|
||||
title: Text('My Repositories'),
|
||||
),
|
||||
),
|
||||
const ListTile(
|
||||
title: Text('Explore'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: BlocProvider(
|
||||
create: (_) => RepoBloc(giteaService: GiteaService(apiAccess: widget.savedUser.apiAccess))..add(RepoFetched()),
|
||||
create: (_) => RepoBloc(
|
||||
giteaService: GiteaService(apiAccess: widget.savedUser.apiAccess))
|
||||
..add(RepoFetched()),
|
||||
child: ReposList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
119
pubspec.lock
119
pubspec.lock
|
@ -36,6 +36,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.0.3"
|
||||
bloc_concurrency:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bloc_concurrency
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -134,6 +141,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -280,6 +294,34 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -287,6 +329,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -308,6 +357,62 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
shared_preferences_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
shared_preferences_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -453,6 +558,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.5.2"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0+1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -42,6 +42,8 @@ dependencies:
|
|||
equatable: ^2.0.3
|
||||
flutter_bloc: ^8.0.1
|
||||
stream_transform: ^2.0.0
|
||||
bloc_concurrency: ^0.2.0
|
||||
shared_preferences: ^2.0.13
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in a new issue