Added Auto-Login

This commit is contained in:
Balazs Toldi 2022-05-09 22:48:51 +02:00
parent 29a89b951c
commit e59db71810
Signed by: Bazsalanszky
GPG key ID: 6C7D440036F99D58
8 changed files with 290 additions and 23 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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