Skip to content

Commit 6df330f

Browse files
authored
Merge pull request #1 from SnipMeDev/release/0.1.0
Release of 0.1.0
2 parents 0a7e1b3 + 60b19b1 commit 6df330f

20 files changed

Lines changed: 11381 additions & 11154 deletions

File tree

README.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
# highlights_plugin
22

3-
Dart implementation of highlights engine.
3+
Dart implementation of highlights KMM engine:
4+
https://github.com/SnipMeDev/Highlights
45

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

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

12-
For help getting started with Flutter development, view the
13-
[online documentation](https://flutter.dev/docs), which offers tutorials,
14-
samples, guidance on mobile development, and a full API reference.
16+
## Support
17+
18+
- Android ✅
19+
- iOS ✅
20+
- macOS 🔴 (Not yet)
21+
- Linux 🔴 (Not yet)
22+
- Windows 🔴 (Not yet)
23+
- Web 🔴 (Not yet)
1524

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ android {
4646
}
4747

4848
dependencies {
49-
implementation "dev.snipme:highlights:0.9.2"
49+
implementation "dev.snipme:highlights:1.0.0"
5050
}
Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package pl.tkadziolka.highlights_plugin
22

3-
import android.util.JsonWriter
3+
import dev.snipme.highlights.DefaultHighlightsResultListener
44
import dev.snipme.highlights.Highlights
5+
import dev.snipme.highlights.internal.phraseLocationSetFromJson
56
import dev.snipme.highlights.internal.toJson
67
import dev.snipme.highlights.model.CodeHighlight
8+
import dev.snipme.highlights.model.PhraseLocation
79
import dev.snipme.highlights.model.SyntaxLanguage
10+
import dev.snipme.highlights.model.SyntaxTheme
811
import dev.snipme.highlights.model.SyntaxThemes
912
import io.flutter.embedding.engine.plugins.FlutterPlugin
10-
import io.flutter.plugin.common.JSONUtil
1113
import io.flutter.plugin.common.MethodCall
1214
import io.flutter.plugin.common.MethodChannel
1315
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@@ -21,37 +23,83 @@ class HighlightsPlugin : FlutterPlugin, MethodCallHandler {
2123
/// when the Flutter Engine is detached from the Activity
2224
private lateinit var channel: MethodChannel
2325
private lateinit var highlights: Highlights
26+
private var useDarkMode = false
2427

2528
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
2629
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "highlights_plugin")
2730
channel.setMethodCallHandler(this)
31+
highlights = Highlights.Builder().build()
2832
}
2933

34+
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
35+
channel.setMethodCallHandler(null)
36+
}
37+
38+
// TODO Implement EventChannel
39+
3040
override fun onMethodCall(call: MethodCall, result: Result) {
31-
println("Method call ${call.method}")
3241
when (call.method) {
3342
"getHighlights" -> {
34-
highlights = Highlights.Builder(
35-
code = call.argument("code") ?: "",
36-
language = SyntaxLanguage.getByName(call.argument("language") ?: "")
37-
?: SyntaxLanguage.DEFAULT,
38-
theme = SyntaxThemes.getByName(call.argument("theme") ?: "")
39-
?: SyntaxThemes.default(),
40-
emphasisLocations = call.argument("emphasisLocations") ?: emptyList()
41-
).build()
42-
43-
val highlightList = highlights.getHighlights()
44-
result.success(highlightList.toJson())
43+
updateInstance(
44+
code = call.argument("code"),
45+
language = SyntaxLanguage.getByName(call.argument("language") ?: ""),
46+
theme = SyntaxThemes.getByName(call.argument("theme") ?: "", useDarkMode),
47+
emphasisLocations = tryGetEmphasisFromJson(call.argument("emphasisLocations"))
48+
)
49+
50+
highlights.getHighlightsAsync(
51+
object: DefaultHighlightsResultListener() {
52+
override fun onComplete(highlightList: List<CodeHighlight>) {
53+
result.success(highlightList.toJson())
54+
}
55+
56+
override fun onCancel() {
57+
result.success(null)
58+
}
59+
60+
override fun onError(exception: Throwable) {
61+
result.error("Error", exception.message, null)
62+
}
63+
}
64+
)
4565
}
4666
"getLanguages" -> result.success(SyntaxLanguage.getNames())
47-
"getThemes" -> result.success(SyntaxThemes.getNames())
67+
"getThemes" -> result.success(SyntaxThemes.getNames(useDarkMode))
68+
"setDarkMode" -> {
69+
useDarkMode = call.argument("useDarkMode") ?: false
70+
result.success(null)
71+
}
4872
else -> {
4973
result.notImplemented()
5074
}
5175
}
5276
}
5377

54-
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
55-
channel.setMethodCallHandler(null)
78+
private fun updateInstance(
79+
code: String?,
80+
language: SyntaxLanguage?,
81+
theme: SyntaxTheme?,
82+
emphasisLocations: List<PhraseLocation>?,
83+
) {
84+
println("Update instance $code $language $theme $emphasisLocations")
85+
86+
if (highlights.getLanguage() == language && highlights.getTheme() == theme) {
87+
println("Only change code $code")
88+
println("Only change emphasis $emphasisLocations")
89+
code?.let { highlights.setCode(it) }
90+
emphasisLocations?.let { locations -> locations.forEach { highlights.setEmphasis(it) } }
91+
} else {
92+
highlights = Highlights.Builder(
93+
code = code ?: "",
94+
language = language ?: SyntaxLanguage.DEFAULT,
95+
theme = theme ?: SyntaxThemes.default(useDarkMode),
96+
emphasisLocations = emphasisLocations ?: emptyList()
97+
).build()
98+
}
99+
}
100+
101+
private fun tryGetEmphasisFromJson(json: String?): List<PhraseLocation> {
102+
if (json == null) return emptyList()
103+
return json.phraseLocationSetFromJson().toList()
56104
}
57105
}

example/lib/main.dart

Lines changed: 112 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import 'package:flutter/material.dart';
22
import 'package:highlights_plugin/highlights_plugin.dart';
3+
import 'package:highlights_plugin/model/phrase_location.dart';
34

5+
// TODO Test code
6+
// TODO Add changelog
47
void main() {
58
runApp(const MyApp());
69
}
@@ -19,6 +22,12 @@ class _MyAppState extends State<MyApp> {
1922
String? _language;
2023
String? _theme;
2124
List<String> _highlights = [];
25+
final List<PhraseLocation> _emphasis = [];
26+
27+
Future<void> _updateDarkMode(bool isDark) async {
28+
_highlightsPlugin.setDarkMode(isDark);
29+
_updateHighlights(_code ?? '');
30+
}
2231

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

3746
Future<void> _updateHighlights(String code) async {
47+
print('Updating highlights $code');
3848
_code = code;
39-
final highlightList = await _highlightsPlugin.getHighlights(
49+
_highlightsPlugin
50+
.getHighlights(
4051
_code ?? '',
41-
_language ?? '',
42-
_theme ?? '',
43-
[],
44-
);
45-
setState(() {
46-
_highlights =
47-
highlightList.map((highlight) => highlight.toString()).toList();
52+
_language,
53+
_theme,
54+
_emphasis,
55+
)
56+
.then((value) {
57+
print('Flutter Highlights $value');
58+
setState(() {
59+
value.sort((a, b) => a.location.start.compareTo(b.location.start));
60+
_highlights = value.map((highlight) => highlight.toString()).toList();
61+
});
4862
});
4963
}
5064

65+
void _addEmphasis(PhraseLocation location) {
66+
_emphasis.add(location);
67+
_updateHighlights(_code ?? '');
68+
}
69+
5170
@override
5271
Widget build(BuildContext context) {
5372
return MaterialApp(
@@ -59,13 +78,11 @@ class _MyAppState extends State<MyApp> {
5978
crossAxisAlignment: CrossAxisAlignment.stretch,
6079
children: [
6180
Expanded(
62-
child: TextField(
63-
onChanged: _updateHighlights,
64-
maxLines: 20,
65-
keyboardType: TextInputType.multiline,
66-
decoration: const InputDecoration(
67-
border: OutlineInputBorder(),
68-
),
81+
child: _EditableTextField(
82+
onChange: (code) => _updateHighlights(code),
83+
onBold: (location) {
84+
_addEmphasis(location);
85+
},
6986
),
7087
),
7188
Expanded(
@@ -79,13 +96,15 @@ class _MyAppState extends State<MyApp> {
7996
)
8097
],
8198
),
82-
// TODO Add theme switcher
8399
bottomNavigationBar: Padding(
84100
padding: const EdgeInsets.all(16),
85101
child: Column(
86102
mainAxisSize: MainAxisSize.min,
87103
crossAxisAlignment: CrossAxisAlignment.stretch,
88104
children: <Widget>[
105+
_ThemeSwitchRow(
106+
onChange: (isDark) => _updateDarkMode(isDark),
107+
),
89108
FutureDropdown(
90109
selected: _language,
91110
future: _highlightsPlugin.getLanguages(),
@@ -105,6 +124,83 @@ class _MyAppState extends State<MyApp> {
105124
}
106125
}
107126

127+
class _EditableTextField extends StatelessWidget {
128+
const _EditableTextField({
129+
required this.onChange,
130+
required this.onBold,
131+
});
132+
133+
final void Function(String) onChange;
134+
final void Function(PhraseLocation) onBold;
135+
136+
@override
137+
Widget build(BuildContext context) {
138+
return TextField(
139+
onChanged: onChange,
140+
maxLines: 20,
141+
keyboardType: TextInputType.multiline,
142+
decoration: const InputDecoration(
143+
border: OutlineInputBorder(),
144+
),
145+
contextMenuBuilder: (context, state) {
146+
final TextEditingValue value = state.textEditingValue;
147+
final List<ContextMenuButtonItem> buttonItems =
148+
state.contextMenuButtonItems;
149+
buttonItems.insert(
150+
0,
151+
ContextMenuButtonItem(
152+
label: 'Bold',
153+
onPressed: () {
154+
ContextMenuController.removeAny();
155+
final range = value.selection;
156+
onBold(PhraseLocation(start: range.start, end: range.end));
157+
},
158+
),
159+
);
160+
return AdaptiveTextSelectionToolbar.buttonItems(
161+
anchors: state.contextMenuAnchors,
162+
buttonItems: buttonItems,
163+
);
164+
},
165+
);
166+
}
167+
}
168+
169+
class _ThemeSwitchRow extends StatefulWidget {
170+
const _ThemeSwitchRow({required this.onChange});
171+
172+
final void Function(bool) onChange;
173+
174+
@override
175+
State<_ThemeSwitchRow> createState() => _ThemeSwitchRowState();
176+
}
177+
178+
class _ThemeSwitchRowState extends State<_ThemeSwitchRow> {
179+
var isDark = false;
180+
181+
@override
182+
Widget build(BuildContext context) {
183+
void onChanged(bool value) {
184+
widget.onChange(value);
185+
setState(() {
186+
isDark = value;
187+
});
188+
}
189+
190+
return Row(
191+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
192+
children: [
193+
const Icon(Icons.brightness_4),
194+
Switch(
195+
value: isDark,
196+
onChanged: (isDark) => onChanged(isDark),
197+
),
198+
const Icon(Icons.brightness_2),
199+
],
200+
);
201+
}
202+
}
203+
108204
class FutureDropdown<T> extends StatelessWidget {
109205
const FutureDropdown({
110206
required this.selected,

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ packages:
8686
path: ".."
8787
relative: true
8888
source: path
89-
version: "0.0.1"
89+
version: "0.1.0"
9090
json_annotation:
9191
dependency: transitive
9292
description:

0 commit comments

Comments
 (0)