Compare commits

..

4 commits

Author SHA1 Message Date
26dd27a435 Update readme to reflect current state 2024-11-08 00:37:06 +01:00
98b8e2b3bf Update Features, add todos, remove wrong comments
Maybe next Time I should reread the spec, instead
of relying on memory from half a year ago
2024-11-08 00:19:31 +01:00
a28817a93d First working version of app-link integration 2024-11-08 00:15:39 +01:00
2cc099d2ae Update README to reflect current state and link feature list 2024-11-07 02:00:27 +01:00
12 changed files with 167 additions and 54 deletions

View file

@ -2,6 +2,18 @@
A shy Gopher client made with Flutter for Android (and maybe desktop)
Not even in Alpha state, it can browse text files over Gopher but it's not usable yet
In Alpha state, you can use it if you're tough, for more see Current Usability
Eventually will be on F-Droid
## Current usability status: kinda usable
You can navigate GoperDirectories, read plaintext files and go back in navigation stack.
App starts when you click on ``gopher://`` link.
List of [features and TODOs](/features.org)
[Link to test gopher page](gopher://treebrary.org/Directory/) because it's hard to find gopher link on the web these days for some
reason.
[Link to test page with type inference](gopher://treebrary.org/0/Directory/)

View file

@ -1,44 +1,53 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="gophershy"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
<application
android:label="gophershy"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Add optional android:host to distinguish your app
from others in case of conflicting scheme name -->
<!-- <data android:scheme="sample" android:host="open.my.app" /> -->
<data android:scheme="gopher" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View file

@ -1,7 +1,7 @@
* Features non exhaustive list of TODOs
Some todos collected from source code, sorted roughly in the order of importance
- [-] *Most important features* [0%]
- [-] *Most important features* [9%]
- [-] Navigation stack with loaded items and urls [1/3]
- [X] Capture the back key
- [ ] Add buttons to go back & forth
@ -10,7 +10,7 @@ Some todos collected from source code, sorted roughly in the order of importance
- [ ] Add toolbar with disable wrap button
- [ ] Menu should be nonwrapping by default
- [ ] Add option to wrap only filenames and not info items as they may be just decoration
- [ ] [[https://github.com/llfbandit/app_links/blob/master/doc/README_android.md][Register to handle]] ~gopher://~ protocol
- [X] [[https://github.com/llfbandit/app_links/blob/master/doc/README_android.md][Register to handle]] ~gopher://~ protocol
- [ ] Keep toolbar url up to date
- [ ] Fix line reading from socket
- [ ] Bookmarks

View file

@ -21,6 +21,7 @@ class _GopherBrowserState extends State<GopherBrowser> {
@override
void initState() {
super.initState();
print("Started with: ${widget.initialUrl}");
_currentItem = ParsedGopherItem.parseUrl(widget.initialUrl);
}

View file

@ -169,8 +169,6 @@ class ParsedGopherItem {
for (int i = 0; i < rcvd.length; i++) {
if (rcvd[i] == "\n") {
var menuLine = rcvd.substring(start, i);
// TODO: Gophernicus sends .\r for some reason which doesnt seem acording to spec
// Maybe ignore when sent from menu=forced?
if (menuLine.startsWith(".")) {
print("Received starting dot, this should be the end I guess");
return;
@ -180,7 +178,7 @@ class ParsedGopherItem {
start = i + 1;
}
}
});
});
await subscription.asFuture<void>();
sock.close();
return LoadedGopherItem(

View file

@ -2,8 +2,10 @@
// once it will matter
// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:app_links/app_links.dart';
import 'package:gophershy/gopher_browser.dart';
@ -11,13 +13,45 @@ void main() {
runApp(const GopherShy());
}
class GopherShy extends StatelessWidget {
const GopherShy({super.key});
class _GopherShyState extends State<GopherShy> {
late final AppLinks _appLinks;
final _navigatorKey = GlobalKey<NavigatorState>();
StreamSubscription<Uri>? _linkSubscription;
@override
void initState() {
super.initState();
_appLinks = AppLinks();
_initDeepLinks();
print("Launching bcs: ${widget.fromIntentLink}");
}
@override
void dispose() {
_linkSubscription?.cancel();
super.dispose();
}
Future<void> _initDeepLinks() async {
// Handle links
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
debugPrint('GopherShy: onAppLink: $uri');
_openAppLink(uri);
});
}
void _openAppLink(Uri uri) {
_navigatorKey.currentState?.push(MaterialPageRoute<void>(
builder: (BuildContext context) {
return GopherShy(fromIntentLink: uri.toString());
},
));
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navigatorKey,
title: 'GopherShy',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
@ -28,13 +62,22 @@ class GopherShy extends StatelessWidget {
appBar: PreferredSize(
preferredSize: Size.fromHeight(50),
child: Text(
"Whoaaaaa",
"${widget.fromIntentLink} Whoaaaaa",
style: TextStyle(fontSize: 50),
),
),
// In future replace with bookmark view
body: GopherBrowser(initialUrl: "gopher://treebrary.org"),
body: GopherBrowser( //TODO: replace default url with homepage from prefs
initialUrl: widget.fromIntentLink ?? "gopher://treebrary.org"),
)),
);
}
}
class GopherShy extends StatefulWidget {
const GopherShy({super.key, this.fromIntentLink});
final String? fromIntentLink;
@override
State<GopherShy> createState() => _GopherShyState();
}

View file

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <gtk/gtk_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar);
}

View file

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
gtk
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View file

@ -1,6 +1,38 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
app_links:
dependency: "direct main"
description:
name: app_links
sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99
url: "https://pub.dev"
source: hosted
version: "6.3.2"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.dev"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.dev"
source: hosted
version: "1.0.4"
async:
dependency: transitive
description:
@ -88,6 +120,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
gtk:
dependency: transitive
description:
name: gtk
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
url: "https://pub.dev"
source: hosted
version: "2.1.0"
leak_tracker:
dependency: transitive
description:

View file

@ -25,6 +25,7 @@ environment:
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
app_links: ^6.3.2
flutter:
sdk: flutter
shared_preferences: ^2.2.3

View file

@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
}

View file

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
app_links
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST