Skip to content

Commit

Permalink
Merge pull request #1 from SnipMeDev/release/0.1.0
Browse files Browse the repository at this point in the history
Release of 0.1.0
  • Loading branch information
tmaxxdd authored Oct 1, 2024
2 parents 0a7e1b3 + 60b19b1 commit 6df330f
Show file tree
Hide file tree
Showing 20 changed files with 11,381 additions and 11,154 deletions.
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
# highlights_plugin

Dart implementation of highlights engine.
Dart implementation of highlights KMM engine:
https://github.com/SnipMeDev/Highlights

## Getting Started
<img width="250" src="https://github.com/user-attachments/assets/e28639c1-e1a5-47d2-9a39-d1a3f2973651"/>
<img width="250" src="https://github.com/user-attachments/assets/2a0239b5-bacd-4173-9d8b-697ef37fba05"/>

This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/developing-packages/),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
## Features
- 17 supported languages (Kotlin, Dart, Swift, PHP, etc)
- Light / dark mode
- 6 built-in themes
- Phrase bolding (emphasis)
- Result caching and support for incremental changes

For help getting started with Flutter development, view the
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
## Support

- Android ✅
- iOS ✅
- macOS 🔴 (Not yet)
- Linux 🔴 (Not yet)
- Windows 🔴 (Not yet)
- Web 🔴 (Not yet)

2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ android {
}

dependencies {
implementation "dev.snipme:highlights:0.9.2"
implementation "dev.snipme:highlights:1.0.0"
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package pl.tkadziolka.highlights_plugin

import android.util.JsonWriter
import dev.snipme.highlights.DefaultHighlightsResultListener
import dev.snipme.highlights.Highlights
import dev.snipme.highlights.internal.phraseLocationSetFromJson
import dev.snipme.highlights.internal.toJson
import dev.snipme.highlights.model.CodeHighlight
import dev.snipme.highlights.model.PhraseLocation
import dev.snipme.highlights.model.SyntaxLanguage
import dev.snipme.highlights.model.SyntaxTheme
import dev.snipme.highlights.model.SyntaxThemes
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.JSONUtil
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
Expand All @@ -21,37 +23,83 @@ class HighlightsPlugin : FlutterPlugin, MethodCallHandler {
/// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel
private lateinit var highlights: Highlights
private var useDarkMode = false

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "highlights_plugin")
channel.setMethodCallHandler(this)
highlights = Highlights.Builder().build()
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}

// TODO Implement EventChannel

override fun onMethodCall(call: MethodCall, result: Result) {
println("Method call ${call.method}")
when (call.method) {
"getHighlights" -> {
highlights = Highlights.Builder(
code = call.argument("code") ?: "",
language = SyntaxLanguage.getByName(call.argument("language") ?: "")
?: SyntaxLanguage.DEFAULT,
theme = SyntaxThemes.getByName(call.argument("theme") ?: "")
?: SyntaxThemes.default(),
emphasisLocations = call.argument("emphasisLocations") ?: emptyList()
).build()

val highlightList = highlights.getHighlights()
result.success(highlightList.toJson())
updateInstance(
code = call.argument("code"),
language = SyntaxLanguage.getByName(call.argument("language") ?: ""),
theme = SyntaxThemes.getByName(call.argument("theme") ?: "", useDarkMode),
emphasisLocations = tryGetEmphasisFromJson(call.argument("emphasisLocations"))
)

highlights.getHighlightsAsync(
object: DefaultHighlightsResultListener() {
override fun onComplete(highlightList: List<CodeHighlight>) {
result.success(highlightList.toJson())
}

override fun onCancel() {
result.success(null)
}

override fun onError(exception: Throwable) {
result.error("Error", exception.message, null)
}
}
)
}
"getLanguages" -> result.success(SyntaxLanguage.getNames())
"getThemes" -> result.success(SyntaxThemes.getNames())
"getThemes" -> result.success(SyntaxThemes.getNames(useDarkMode))
"setDarkMode" -> {
useDarkMode = call.argument("useDarkMode") ?: false
result.success(null)
}
else -> {
result.notImplemented()
}
}
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
private fun updateInstance(
code: String?,
language: SyntaxLanguage?,
theme: SyntaxTheme?,
emphasisLocations: List<PhraseLocation>?,
) {
println("Update instance $code $language $theme $emphasisLocations")

if (highlights.getLanguage() == language && highlights.getTheme() == theme) {
println("Only change code $code")
println("Only change emphasis $emphasisLocations")
code?.let { highlights.setCode(it) }
emphasisLocations?.let { locations -> locations.forEach { highlights.setEmphasis(it) } }
} else {
highlights = Highlights.Builder(
code = code ?: "",
language = language ?: SyntaxLanguage.DEFAULT,
theme = theme ?: SyntaxThemes.default(useDarkMode),
emphasisLocations = emphasisLocations ?: emptyList()
).build()
}
}

private fun tryGetEmphasisFromJson(json: String?): List<PhraseLocation> {
if (json == null) return emptyList()
return json.phraseLocationSetFromJson().toList()
}
}
128 changes: 112 additions & 16 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:highlights_plugin/highlights_plugin.dart';
import 'package:highlights_plugin/model/phrase_location.dart';

// TODO Test code
// TODO Add changelog
void main() {
runApp(const MyApp());
}
Expand All @@ -19,6 +22,12 @@ class _MyAppState extends State<MyApp> {
String? _language;
String? _theme;
List<String> _highlights = [];
final List<PhraseLocation> _emphasis = [];

Future<void> _updateDarkMode(bool isDark) async {
_highlightsPlugin.setDarkMode(isDark);
_updateHighlights(_code ?? '');
}

void _updateLanguage(String language) {
setState(() {
Expand All @@ -35,19 +44,29 @@ class _MyAppState extends State<MyApp> {
}

Future<void> _updateHighlights(String code) async {
print('Updating highlights $code');
_code = code;
final highlightList = await _highlightsPlugin.getHighlights(
_highlightsPlugin
.getHighlights(
_code ?? '',
_language ?? '',
_theme ?? '',
[],
);
setState(() {
_highlights =
highlightList.map((highlight) => highlight.toString()).toList();
_language,
_theme,
_emphasis,
)
.then((value) {
print('Flutter Highlights $value');
setState(() {
value.sort((a, b) => a.location.start.compareTo(b.location.start));
_highlights = value.map((highlight) => highlight.toString()).toList();
});
});
}

void _addEmphasis(PhraseLocation location) {
_emphasis.add(location);
_updateHighlights(_code ?? '');
}

@override
Widget build(BuildContext context) {
return MaterialApp(
Expand All @@ -59,13 +78,11 @@ class _MyAppState extends State<MyApp> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: TextField(
onChanged: _updateHighlights,
maxLines: 20,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
child: _EditableTextField(
onChange: (code) => _updateHighlights(code),
onBold: (location) {
_addEmphasis(location);
},
),
),
Expanded(
Expand All @@ -79,13 +96,15 @@ class _MyAppState extends State<MyApp> {
)
],
),
// TODO Add theme switcher
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
_ThemeSwitchRow(
onChange: (isDark) => _updateDarkMode(isDark),
),
FutureDropdown(
selected: _language,
future: _highlightsPlugin.getLanguages(),
Expand All @@ -105,6 +124,83 @@ class _MyAppState extends State<MyApp> {
}
}

class _EditableTextField extends StatelessWidget {
const _EditableTextField({
required this.onChange,
required this.onBold,
});

final void Function(String) onChange;
final void Function(PhraseLocation) onBold;

@override
Widget build(BuildContext context) {
return TextField(
onChanged: onChange,
maxLines: 20,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
contextMenuBuilder: (context, state) {
final TextEditingValue value = state.textEditingValue;
final List<ContextMenuButtonItem> buttonItems =
state.contextMenuButtonItems;
buttonItems.insert(
0,
ContextMenuButtonItem(
label: 'Bold',
onPressed: () {
ContextMenuController.removeAny();
final range = value.selection;
onBold(PhraseLocation(start: range.start, end: range.end));
},
),
);
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: state.contextMenuAnchors,
buttonItems: buttonItems,
);
},
);
}
}

class _ThemeSwitchRow extends StatefulWidget {
const _ThemeSwitchRow({required this.onChange});

final void Function(bool) onChange;

@override
State<_ThemeSwitchRow> createState() => _ThemeSwitchRowState();
}

class _ThemeSwitchRowState extends State<_ThemeSwitchRow> {
var isDark = false;

@override
Widget build(BuildContext context) {
void onChanged(bool value) {
widget.onChange(value);
setState(() {
isDark = value;
});
}

return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Icon(Icons.brightness_4),
Switch(
value: isDark,
onChanged: (isDark) => onChanged(isDark),
),
const Icon(Icons.brightness_2),
],
);
}
}

class FutureDropdown<T> extends StatelessWidget {
const FutureDropdown({
required this.selected,
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.0.1"
version: "0.1.0"
json_annotation:
dependency: transitive
description:
Expand Down
Loading

0 comments on commit 6df330f

Please sign in to comment.