Basic Repo overview

This commit is contained in:
Bazsalanszky 2022-05-15 13:17:53 +02:00
parent 612e5aad18
commit 0f052d018c
Signed by: Bazsalanszky
GPG key ID: B40814F4EFE23F96
11 changed files with 437 additions and 52 deletions

View file

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:gitea_client/model/repouser.dart';
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:gitea_client/widget/repo_overview.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'model/ApiAccess.dart';
@ -23,11 +25,10 @@ class MyApp extends StatelessWidget {
primarySwatch: Colors.lightGreen,
),
home: MyHomePage(),
routes: {
},
routes: {},
onGenerateRoute: (route) {
switch (route.name) { // <- here
switch (route.name) {
// <- here
case "/loginstatus":
return MaterialPageRoute(
settings: const RouteSettings(name: "/parameterpage"),
@ -38,12 +39,18 @@ class MyApp extends StatelessWidget {
case "/repolist":
return MaterialPageRoute(
settings: const RouteSettings(name: "/repolist"),
builder: (context) => RepoListPage(savedUser: route.arguments as SavedUser),
builder: (context) =>
RepoListPage(savedUser: route.arguments as SavedUser),
);
case "/repopage":
final repouser = route.arguments as RepoUser;
return MaterialPageRoute(
settings: const RouteSettings(name: "/repopage"),
builder: (context) =>
RepoPage(repo: repouser.repository, user: repouser.user));
}
return null;
},
);
}
}
@ -54,17 +61,15 @@ class MyHomePage extends StatefulWidget {
}
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) {
if (token != null && instance != null) {
await prefs.setBool("autoLogin", true);
setState(() {
loggedIn = true;
@ -83,12 +88,12 @@ class _MyHomePage extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return (loggedIn) ? StatefulLoginStatus(apiAccess: _apiAccess!) : const LoginPage(title: "Login to Gitea");
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);
@ -118,4 +123,4 @@ class LoginPage extends StatelessWidget {
),
));
}
}
}

89
lib/model/File.dart Normal file
View file

@ -0,0 +1,89 @@
class RepoFile {
String name;
String path;
String sha;
String type;
int size;
String? encoding;
String? content;
String? target;
String url;
String htmlUrl;
String gitUrl;
String? downloadUrl;
String? submoduleGitUrl;
Links lLinks;
RepoFile(
{required this.name,
required this.path,
required this.sha,
required this.type,
required this.size,
this.encoding,
this.content,
this.target,
required this.url,
required this.htmlUrl,
required this.gitUrl,
this.downloadUrl,
this.submoduleGitUrl,
required this.lLinks});
RepoFile.fromJson(Map<String, dynamic> json)
: name = json['name'],
path = json['path'],
sha = json['sha'],
type = json['type'],
size = json['size'],
url = json['url'],
htmlUrl = json['html_url'],
gitUrl = json['git_url'],
lLinks = Links.fromJson(json['_links']) {
encoding = json['encoding'];
content = json['content'];
target = json['target'];
downloadUrl = json['download_url'];
submoduleGitUrl = json['submodule_git_url'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['name'] = name;
data['path'] = path;
data['sha'] = sha;
data['type'] = type;
data['size'] = size;
data['encoding'] = encoding;
data['content'] = content;
data['target'] = target;
data['url'] = url;
data['html_url'] = htmlUrl;
data['git_url'] = gitUrl;
data['download_url'] = downloadUrl;
data['submodule_git_url'] = submoduleGitUrl;
data['_links'] = lLinks.toJson();
return data;
}
}
class Links {
String self;
String git;
String html;
Links({required this.self, required this.git, required this.html});
Links.fromJson(Map<String, dynamic> json)
: self = json['self'],
git = json['git'],
html = json['html'];
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['self'] = self;
data['git'] = git;
data['html'] = html;
return data;
}
}

10
lib/model/repouser.dart Normal file
View file

@ -0,0 +1,10 @@
import 'package:gitea_client/model/repository.dart';
import 'package:gitea_client/model/user.dart';
class RepoUser {
final Repository repository;
final SavedUser user;
RepoUser(this.repository, this.user);
}

View file

@ -6,6 +6,8 @@ import 'package:gitea_client/model/repository.dart';
import 'package:gitea_client/model/user.dart';
import 'package:http/http.dart' as http;
import '../model/File.dart';
User _parseAuthenticatedUserResponse(String message){
return User.fromJson(jsonDecode(message));
}
@ -23,6 +25,16 @@ class GiteaService {
);
return compute(_parseAuthenticatedUserResponse, response.body);
}
Future<RepoFile> getFile(String owner,String repo,String path) async{
var response = await http.get(
Uri.https(apiAccess.instance, "api/v1/repos/$owner/$repo/contents/$path", {
"token": apiAccess.token,
}),
);
return RepoFile.fromJson(jsonDecode(response.body));
}
//
Future<List<Repository>> getUserRepositories([int page = 1, int limit = 10]) async{
var response = await http.get(

View file

@ -113,17 +113,15 @@ class _StatefulLoginStatus extends State<StatefulLoginStatus> {
await prefs.setString('token', apiAccess.token);
}
void _autoLogin() async{
void _autoLogin() async {
prefs = await SharedPreferences.getInstance();
bool? autologin = prefs.getBool("autoLogin");
if(autologin != null){
if (autologin != null) {
userRequest!.then((user) => {
Navigator.pushNamed(context, "/repolist",
arguments: SavedUser(
authedUser: user,
apiAccess: widget.apiAccess))
});
Navigator.of(context).pushReplacementNamed("/repolist",
arguments:
SavedUser(authedUser: user, apiAccess: widget.apiAccess))
});
}
}
}

View file

@ -1,11 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gitea_client/cubit/repo_event.dart';
import 'package:gitea_client/model/ApiAccess.dart';
import 'package:gitea_client/model/repository.dart';
import 'package:gitea_client/model/repouser.dart';
import 'package:gitea_client/model/user.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../cubit/repo_cubit.dart';
class ReposList extends StatefulWidget {
final SavedUser user;
const ReposList({Key? key, required this.user}) : super(key: key);
@override
_ReposListState createState() => _ReposListState();
}
@ -31,14 +38,14 @@ class _ReposListState extends State<ReposList> {
if (state.repos.isEmpty) {
return const Center(child: Text('no repos'));
}
if(state.repos.length < 5) {
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(repo: state.repos[index]);
: RepoListItem(repo: state.repos[index],user: widget.user,);
},
itemCount: state.hasReachedMax
? state.repos.length
@ -86,10 +93,10 @@ class BottomLoader extends StatelessWidget {
}
class RepoListItem extends StatelessWidget {
const RepoListItem({Key? key, required this.repo}) : super(key: key);
const RepoListItem({Key? key, required this.repo, required this.user}) : super(key: key);
final Repository repo;
final SavedUser user;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
@ -97,24 +104,34 @@ class RepoListItem extends StatelessWidget {
return Material(
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()))),
child: ListTile(
leading: (repo.private!)
? const Icon(Icons.lock)
: (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: () => {
Navigator.pushNamed(context, "/repopage",arguments: RepoUser(repo,user))
},
),
),
);
}
}
}

View file

@ -65,7 +65,7 @@ class _RepoListPage extends State<RepoListPage> {
create: (_) => RepoBloc(
giteaService: GiteaService(apiAccess: widget.savedUser.apiAccess))
..add(RepoFetched()),
child: ReposList(),
child: ReposList(user: widget.savedUser, ),
),
);
}

View file

@ -0,0 +1,223 @@
import 'dart:convert';
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:gitea_client/model/repository.dart';
import 'package:gitea_client/model/user.dart';
import 'package:gitea_client/service/gitea_service.dart';
import '../model/File.dart';
class RepoPage extends StatefulWidget {
final Repository repo;
final SavedUser user;
const RepoPage({Key? key, required this.repo, required this.user})
: super(key: key);
@override
_RepoPage createState() => _RepoPage();
}
class _RepoPage extends State<RepoPage> {
int _currentIndex = 0;
late final List _screens;
@override
void initState() {
_screens = [
RepoHome(
repo: widget.repo,
user: widget.user,
),
RepoFiles(
repo: widget.repo,
user: widget.user,
),
RepoIssues(
repo: widget.repo,
user: widget.user,
),
RepoPullRequests(
repo: widget.repo,
user: widget.user,
)
];
}
void _updateIndex(int value) {
setState(() {
_currentIndex = value;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.repo.name),
),
body: _screens[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
onTap: _updateIndex,
selectedItemColor: Colors.green[700],
selectedFontSize: 13,
unselectedFontSize: 13,
iconSize: 30,
items: [
const BottomNavigationBarItem(
label: "Home",
icon: Icon(Icons.home),
),
const BottomNavigationBarItem(
label: "Files",
icon: Icon(Icons.folder),
),
if (widget.repo.hasIssues!)
BottomNavigationBarItem(
label: "Issues",
icon: (widget.repo.openIssuesCount! > 0)
? Badge(
badgeContent:
Text(widget.repo.openIssuesCount!.toString()),
child: const Icon(Icons.error_outline),
)
: const Icon(Icons.error_outline),
),
if (widget.repo.hasPullRequests!)
BottomNavigationBarItem(
label: "Pull requests",
icon: (widget.repo.openPrCounter! > 0)
? Badge(
badgeContent: Text(widget.repo.openPrCounter!.toString()),
child: const Icon(Icons.mediation_rounded),
)
: const Icon(Icons.mediation_rounded),
),
],
),
);
}
}
class RepoHome extends StatefulWidget {
final Repository repo;
final SavedUser user;
const RepoHome({Key? key, required this.repo, required this.user})
: super(key: key);
@override
_RepoHome createState() => _RepoHome();
}
class _RepoHome extends State<RepoHome> {
@override
Widget build(BuildContext context) {
final media = MediaQuery.of(context).size;
return Column(children: [
Text(widget.repo.fullName!,
style: Theme.of(context).textTheme.headline3,),
FutureBuilder<RepoFile>(
future: readmeRequest,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Column(
children: [
Text("No Readme file found!",
style: Theme.of(context).textTheme.headline6),
],
),
);
} else if (snapshot.hasData) {
var file = snapshot.data!;
final content = utf8.decode(base64.decode(file.content!));
return SingleChildScrollView(child: Center(
child: SizedBox(
width: (media.width > 600) ? media.width * 0.6 : media.width*0.9,
height: media.height*0.7,
child: Markdown(selectable: true, data: content)),
));
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
})
]);
}
Future<RepoFile>? readmeRequest;
late final GiteaService giteaService;
@override
void initState() {
giteaService = GiteaService(apiAccess: widget.user.apiAccess);
readmeRequest = giteaService.getFile(
widget.repo.owner.username!, widget.repo.name, "README.md");
}
}
class RepoFiles extends StatefulWidget {
final Repository repo;
final SavedUser user;
const RepoFiles({Key? key, required this.repo, required this.user})
: super(key: key);
@override
_RepoFiles createState() => _RepoFiles();
}
class _RepoFiles extends State<RepoFiles> {
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
class RepoIssues extends StatefulWidget {
final Repository repo;
final SavedUser user;
const RepoIssues({Key? key, required this.repo, required this.user})
: super(key: key);
@override
_RepoIssues createState() => _RepoIssues();
}
class _RepoIssues extends State<RepoIssues> {
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
class RepoPullRequests extends StatefulWidget {
final Repository repo;
final SavedUser user;
const RepoPullRequests({Key? key, required this.repo, required this.user})
: super(key: key);
@override
_RepoPullRequests createState() => _RepoPullRequests();
}
class _RepoPullRequests extends State<RepoPullRequests> {
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}

View file

@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View file

@ -29,6 +29,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
badges:
dependency: "direct main"
description:
name: badges
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
bloc:
dependency: transitive
description:
@ -98,7 +105,7 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
version: "1.16.0"
convert:
dependency: transitive
description:
@ -140,7 +147,7 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.0"
ffi:
dependency: transitive
description:
@ -179,6 +186,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_markdown:
dependency: "direct main"
description:
name: flutter_markdown
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.10"
flutter_test:
dependency: "direct dev"
description: flutter
@ -223,7 +237,7 @@ packages:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
version: "0.6.4"
json_annotation:
dependency: transitive
description:
@ -252,6 +266,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
markdown:
dependency: transitive
description:
name: markdown
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
matcher:
dependency: transitive
description:
@ -265,7 +286,7 @@ packages:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
version: "0.1.4"
meta:
dependency: transitive
description:
@ -293,7 +314,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.1"
path_provider_linux:
dependency: transitive
description:
@ -438,7 +459,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
stack_trace:
dependency: transitive
description:
@ -480,7 +501,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.8"
version: "0.4.9"
typed_data:
dependency: transitive
description:
@ -550,7 +571,7 @@ packages:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "2.1.2"
watcher:
dependency: transitive
description:
@ -580,5 +601,5 @@ packages:
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.16.2 <3.0.0"
dart: ">=2.17.0-0 <3.0.0"
flutter: ">=2.10.0"

View file

@ -44,6 +44,8 @@ dependencies:
stream_transform: ^2.0.0
bloc_concurrency: ^0.2.0
shared_preferences: ^2.0.13
badges: ^2.0.2
flutter_markdown: ^0.6.10
dev_dependencies:
flutter_test: