diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 0000000..d007606 --- /dev/null +++ b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,23 @@ +package io.flutter.plugins; + +import io.flutter.plugin.common.PluginRegistry; + +/** + * Generated file. Do not edit. + */ +public final class GeneratedPluginRegistrant { + public static void registerWith(PluginRegistry registry) { + if (alreadyRegisteredWith(registry)) { + return; + } + } + + private static boolean alreadyRegisteredWith(PluginRegistry registry) { + final String key = GeneratedPluginRegistrant.class.getCanonicalName(); + if (registry.hasPlugin(key)) { + return true; + } + registry.registrarFor(key); + return false; + } +} diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..1bbfff8 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,68 @@ +group 'np.com.sarbagyastha.flutter_rating_bar' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'np.com.sarbagyastha.flutter_rating_bar' + } + + compileSdkVersion 33 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' + } + + defaultConfig { + minSdkVersion 19 + } + + dependencies { + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..8e5c540 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_rating_bar' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fb773e0 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/kotlin/np/com/sarbagyastha/flutter_rating_bar/FlutterRatingBarPlugin.kt b/android/src/main/kotlin/np/com/sarbagyastha/flutter_rating_bar/FlutterRatingBarPlugin.kt new file mode 100644 index 0000000..18f18b8 --- /dev/null +++ b/android/src/main/kotlin/np/com/sarbagyastha/flutter_rating_bar/FlutterRatingBarPlugin.kt @@ -0,0 +1,35 @@ +package np.com.sarbagyastha.flutter_rating_bar + +import androidx.annotation.NonNull + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +/** FlutterRatingBarPlugin */ +class FlutterRatingBarPlugin: FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel : MethodChannel + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_rating_bar") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/android/src/test/kotlin/np/com/sarbagyastha/flutter_rating_bar/FlutterRatingBarPluginTest.kt b/android/src/test/kotlin/np/com/sarbagyastha/flutter_rating_bar/FlutterRatingBarPluginTest.kt new file mode 100644 index 0000000..99db811 --- /dev/null +++ b/android/src/test/kotlin/np/com/sarbagyastha/flutter_rating_bar/FlutterRatingBarPluginTest.kt @@ -0,0 +1,27 @@ +package np.com.sarbagyastha.flutter_rating_bar + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlin.test.Test +import org.mockito.Mockito + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class FlutterRatingBarPluginTest { + @Test + fun onMethodCall_getPlatformVersion_returnsExpectedValue() { + val plugin = FlutterRatingBarPlugin() + + val call = MethodCall("getPlatformVersion", null) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) + + Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) + } +} diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..3f4ceeb --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Rating Bar Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: RatingBarIndicator( + rating: 2.75, + itemBuilder: (context, index) { + return const Icon( + Icons.star, + color: Colors.amber, + ); + }, + itemCount: 5, + itemSize: 50.0, + direction: Axis.horizontal, + ), + ), + ); + } +} diff --git a/lib/src/rating_bar.dart b/lib/src/rating_bar.dart index da42704..3ee6745 100644 --- a/lib/src/rating_bar.dart +++ b/lib/src/rating_bar.dart @@ -55,6 +55,7 @@ class RatingBar extends StatefulWidget { this.tapOnlyMode = false, this.updateOnDrag = false, this.wrapAlignment = WrapAlignment.start, + this.addScaleAnimation = false, super.key, }) : _itemBuilder = null, _ratingWidget = ratingWidget; @@ -83,6 +84,7 @@ class RatingBar extends StatefulWidget { this.tapOnlyMode = false, this.updateOnDrag = false, this.wrapAlignment = WrapAlignment.start, + this.addScaleAnimation = false, super.key, }) : _itemBuilder = itemBuilder, _ratingWidget = null; @@ -179,6 +181,9 @@ class RatingBar extends StatefulWidget { /// Default is false. final bool updateOnDrag; + // Add or remove animation when rating is updated. + final bool addScaleAnimation; + /// How the item within the [RatingBar] should be placed in the main axis. /// /// For example, if [wrapAlignment] is [WrapAlignment.center], the item in @@ -330,7 +335,7 @@ class _RatingBarState extends State { child: ValueListenableBuilder( valueListenable: _glow, builder: (context, glow, child) { - if (glow && widget.glow) { + if (glow && widget.glow && index < _rating) { final glowColor = widget.glowColor ?? Theme.of(context).colorScheme.secondary; return DecoratedBox( @@ -349,7 +354,14 @@ class _RatingBarState extends State { ), ], ), - child: child, + child: widget.addScaleAnimation + ? AnimatedScale( + scale: 1.15, + curve: Curves.easeIn, + duration: const Duration(milliseconds: 1000), + child: child, + ) + : child, ); } return child!; @@ -387,7 +399,10 @@ class _RatingBarState extends State { } _rating = currentRating.clamp(_minRating, _maxRating); - if (widget.updateOnDrag) widget.onRatingUpdate(iconRating); + if (widget.updateOnDrag) { + widget.onRatingUpdate(iconRating); + // addAnimation(iconRating, _rating); + } setState(() {}); } }