Commit 65803cd5 authored by Fahmi's avatar Fahmi

Initial commit

parents
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "ef1af02aead6fe2414f3aafa5a61087b610e1332"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
- platform: android
create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
- platform: ios
create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
- platform: linux
create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
- platform: macos
create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
- platform: web
create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
- platform: windows
create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
# belajar_flutter
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
# 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
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace "com.example.belajar_flutter"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.belajar_flutter"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {}
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="belajar_flutter"
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>
</manifest>
package com.example.belajar_flutter
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
}
include ":app"
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>
#include "Generated.xcconfig"
#include "Generated.xcconfig"
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Belajar Flutter</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>belajar_flutter</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
#import "GeneratedPluginRegistrant.h"
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}
// import 'package:belajar_flutter/screens/login_screen.dart';
// import 'package:flutter/material.dart';
// class FadePageRoute<T> extends MaterialPageRoute<T> {
// FadePageRoute({
// required super.builder,
// super.settings,
// });
// @override
// Duration get transitionDuration => const Duration(milliseconds: 600);
// @override
// Widget buildTransitions(
// BuildContext context,
// Animation<double> animation,
// Animation<double> secondaryAnimation,
// Widget child,
// ) {
// if (settings.name == LoginScreen.routeName) {
// return child;
// }
// return FadeTransition(
// opacity: animation,
// child: child,
// );
// }
// }
//
// Generated file. Do not edit.
//
// ignore_for_file: directives_ordering
// ignore_for_file: lines_longer_than_80_chars
// ignore_for_file: depend_on_referenced_packages
import 'package:url_launcher_web/url_launcher_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs
void registerPlugins(Registrar registrar) {
UrlLauncherPlugin.registerWith(registrar);
registrar.registerMessageHandler();
}
import 'package:belajar_flutter/page/booksbycategory_page.dart';
import 'package:belajar_flutter/services/auth_service.dart';
import 'package:flutter/material.dart';
import 'package:belajar_flutter/page/login_page.dart';
import 'package:belajar_flutter/page/home_page.dart';
import 'package:belajar_flutter/page/register_page.dart';
import 'package:belajar_flutter/page/dashboard_page.dart';
import 'page/librariandashboard_page.dart';
import 'page/librarianlogin_page.dart';
import 'page/memberlogin_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/home', // Tentukan rute awal
routes: {
'/': (context) => AuthenticationWrapper(),
'/dashboard': (context) => DashboardPage(),
'/librarian_dashboard': (context) => LibrarianDashboard(),
'/home': (context) => HomePage(),
'/register': (context) => RegisterPage(),
'/login/member': (context) =>
MemberLoginPage(), // Ubah rute untuk login member
'/login/librarian': (context) => LibrarianLoginPage(),
});
}
}
// class AuthenticationWrapper extends StatelessWidget {
// @override
// Widget build(BuildContext context) {
// final AuthenticationService _authenticationService =
// AuthenticationService();
// return FutureBuilder(
// future: AuthenticationService.isAuthenticated(),
// builder: (context, snapshot) {
// if (snapshot.connectionState == ConnectionState.waiting) {
// return CircularProgressIndicator();
// } else {
// if (snapshot.data == true) {
// final userRole = _authenticationService
// .getUserRole(); // Misalnya, Anda memiliki metode untuk mendapatkan peran pengguna
// if (userRole == UserRole.member) {
// return DashboardPage(); // Pengguna adalah member, arahkan ke dashboard member
// } else if (userRole == UserRole.librarian) {
// return LibrarianDashboard(); // Pengguna adalah librarian, arahkan ke dashboard librarian
// } else {
// return Scaffold(
// body: Center(
// child: Text('Invalid user role!'),
// ),
// );
// }
// } else {
// return HomePage(); // Pengguna belum login, arahkan ke halaman utama
// }
// }
// },
// );
// }
// }
class AuthenticationWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: AuthenticationService.isAuthenticated(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else {
if (snapshot.data == true) {
// Pengguna sudah login
return FutureBuilder<UserRole?>(
future: AuthenticationService.getUserRole(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else {
if (snapshot.hasData) {
// Peran pengguna sudah didapatkan
UserRole? userRole = snapshot.data;
if (userRole == UserRole.member) {
return DashboardPage(); // Navigasi ke dashboard member
} else if (userRole == UserRole.librarian) {
return LibrarianDashboard(); // Navigasi ke dashboard librarian
} else {
return HomePage(); // Peran tidak dikenali, kembali ke halaman login
}
} else {
return HomePage(); // Gagal mendapatkan peran, kembali ke halaman login
}
}
},
);
} else {
return HomePage(); // Pengguna belum login, arahkan ke homepage
}
}
},
);
}
}
// book_model.dart
import 'category_model.dart';
class Book {
final int id;
final String title;
final String author;
final String publisher;
final Category category;
final int datePublish;
final String image;
final String isbn;
Book({
required this.id,
required this.title,
required this.author,
required this.publisher,
required this.category,
required this.datePublish,
required this.image,
required this.isbn,
});
factory Book.fromJson(Map<String, dynamic> json) {
return Book(
id: json['id'],
title: json['title'],
author: json['author'],
publisher: json['publisher'],
category: Category.fromJson(json['category']),
datePublish: int.parse(json['date_publish'].toString()),
image: json['image'],
isbn: json['isbn'],
);
}
}
// borrowed_book.dart
class BookLoanMember {
final String title;
final String imageUrl;
final String dueDate;
final bool isReturned;
BookLoanMember({
required this.title,
required this.imageUrl,
required this.dueDate,
required this.isReturned,
});
factory BookLoanMember.fromJson(Map<String, dynamic> json) {
return BookLoanMember(
title: json['book_info']['title'],
imageUrl: json['book_info']['image'],
dueDate: json['due_date'],
isReturned: json['is_returned'],
);
}
}
// category_model.dart
class Category {
final String name;
Category({
required this.name,
});
factory Category.fromJson(Map<String, dynamic> json) {
return Category(
name: json['name'],
);
}
}
// user_model.dart
class User {
final String email;
final String firstName;
final String lastName;
User({
required this.email,
required this.firstName,
required this.lastName,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
email: json['email'],
firstName: json['first_name'],
lastName: json['last_name'],
);
}
Map<String, dynamic> toJson() {
return {
'email': email,
'first_name': firstName,
'last_name': lastName,
};
}
}
import 'package:belajar_flutter/page/dashboard_page.dart';
import 'package:flutter/material.dart';
import 'package:belajar_flutter/model/book_model.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class BookDetailScreenPage extends StatelessWidget {
final Book book;
const BookDetailScreenPage({Key? key, required this.book}) : super(key: key);
Future<void> _borrowBook(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String accessToken = prefs.getString('access_token') ?? '';
// Tampilkan dialog untuk meminta input tanggal jatuh tempo
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2101),
);
// Jika pengguna memilih tanggal, kirim permintaan pinjam buku
if (pickedDate != null) {
// Konversi tanggal ke format "YYYY-MM-DD"
String formattedDate =
"${pickedDate.year}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.day.toString().padLeft(2, '0')}";
var payload = {
"due_date": formattedDate, // Menggunakan format "YYYY-MM-DD"
};
// Kirim permintaan pinjam buku ke backend
var response = await http.post(
Uri.parse("http://10.0.2.2:8000/api/member/book-loan/${book.id}/"),
headers: {
"Authorization": "Bearer $accessToken",
"Content-Type": "application/json",
},
body: jsonEncode(payload), // Encode payload data sebagai JSON
);
// Periksa status kode respons
if (response.statusCode == 400) {
// Buku sudah dipinjam, tampilkan Snackbar dengan pesan dari respons
Map<String, dynamic> responseData = json.decode(response.body);
String message = responseData['message'];
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: Duration(seconds: 2),
),
);
} else if (response.statusCode == 201) {
// Buku berhasil dipinjam, tampilkan Snackbar sukses
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Buku berhasil dipinjam'),
duration: Duration(seconds: 2),
),
);
} else {
// Respons tidak diharapkan, tampilkan Snackbar dengan pesan default
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal meminjam buku'),
duration: Duration(seconds: 2),
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(book.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
book.image,
width: 200,
height: 200,
fit: BoxFit.cover,
),
SizedBox(height: 16),
Text(
'Author: ${book.author}',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 8),
Text(
'Category: ${book.category.name}',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 8),
Text(
'Description: ${book.datePublish}',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 8),
Text(
'Published Year: ${book.isbn}',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
_borrowBook(
context); // Panggil fungsi pinjam buku saat tombol ditekan
},
child: Text('Pinjam Buku'),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:belajar_flutter/model/book_model.dart';
class BookDetailScreenLibPage extends StatelessWidget {
final Book book;
const BookDetailScreenLibPage({Key? key, required this.book})
: super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(book.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
book.image,
width: 200,
height: 200,
fit: BoxFit.cover,
),
SizedBox(height: 16),
Text(
'Author: ${book.author}',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 8),
Text(
'Category: ${book.category.name}',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 8),
Text(
'Description: ${book.datePublish}',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 8),
Text(
'Published Year: ${book.isbn}',
style: TextStyle(fontSize: 18),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import '../model/book_model.dart';
class BookDetailPage extends StatelessWidget {
final Book book; // Definisi parameter book
// Konstruktor
const BookDetailPage({Key? key, required this.book}) : super(key: key);
@override
Widget build(BuildContext context) {
// Implementasi tampilan halaman detail buku di sini
return Scaffold(
appBar: AppBar(
title: Text('Book Detail'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Title: ${book.title}'),
Text('Author: ${book.author}'),
Image.network(book.image)
// Tambahkan detail buku lainnya sesuai kebutuhan
],
),
),
);
}
}
import 'package:belajar_flutter/model/bookloanmember_model.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class BookLoanMemberPage extends StatefulWidget {
@override
_BookLoanMemberPageState createState() => _BookLoanMemberPageState();
}
class _BookLoanMemberPageState extends State<BookLoanMemberPage> {
late Future<List<BookLoanMember>> _BookLoanMembers;
@override
void initState() {
super.initState();
_BookLoanMembers = fetchBookLoanMembers();
}
Future<List<BookLoanMember>> fetchBookLoanMembers() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String accessToken = prefs.getString('access_token') ?? '';
final response = await http.get(
Uri.parse('http://10.0.2.2:8000/api/member/list-book-loan/'),
headers: {
"Authorization": "Bearer $accessToken",
},
);
if (response.statusCode == 200) {
final List<dynamic> data = jsonDecode(response.body);
// Filter hanya buku yang belum dikembalikan
List<BookLoanMember> notReturnedBooks = data
.map((item) => BookLoanMember.fromJson(item))
.where((book) => !book.isReturned)
.toList();
return notReturnedBooks;
// return data.map((item) => BookLoanMember.fromJson(item)).toList();
} else {
throw Exception('Failed to load borrowed books');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Loaned Books'),
),
body: FutureBuilder<List<BookLoanMember>>(
future: _BookLoanMembers,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
BookLoanMember book = snapshot.data![index];
return ListTile(
leading: Container(
width: 50, // Sesuaikan dengan lebar yang diinginkan
child: Image.network(
book.imageUrl,
fit: BoxFit.cover,
),
),
title: Text(book.title),
trailing: Text('Returned: ${book.isReturned ? 'Yes' : 'No'}'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Due Date: ${book.dueDate}'),
// Tampilkan status isReturned
],
),
);
},
);
}
},
),
);
}
}
import 'package:flutter/material.dart';
import 'package:belajar_flutter/model/book_model.dart';
import 'bookdetail_page.dart';
import 'booklist_page.dart';
class BooksByCategoryPage extends StatelessWidget {
final String category;
final List<Book> books;
const BooksByCategoryPage({
Key? key,
required this.category,
required this.books,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Books - $category'),
),
body: _buildBookList(books),
);
}
Widget _buildBookList(List<Book> books) {
if (books.isEmpty) {
return Center(child: Text('No books available'));
} else {
return ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(books[index].title),
subtitle: Text(books[index].author),
leading: Image.network(
books[index].image,
width: 50,
height: 50,
),
// Add logic to navigate to book detail page if needed
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
BookDetailScreenPage(book: books[index]),
),
);
},
);
},
);
}
}
}
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class ChangePasswordPage extends StatefulWidget {
@override
_ChangePasswordPageState createState() => _ChangePasswordPageState();
}
class _ChangePasswordPageState extends State<ChangePasswordPage> {
final TextEditingController _oldPasswordController = TextEditingController();
final TextEditingController _newPasswordController = TextEditingController();
bool _isPasswordVisible = false;
Future<void> _changePassword() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String accessToken = prefs.getString('access_token') ?? '';
String oldPassword = _oldPasswordController.text;
String newPassword = _newPasswordController.text;
final response = await http.post(
Uri.parse('http://10.0.2.2:8000/api/change-password/'),
headers: <String, String>{
"Authorization": "Bearer $accessToken",
'Content-Type': "application/json",
},
body: jsonEncode(<String, String>{
'old_password': oldPassword,
'new_password': newPassword,
}),
);
if (response.statusCode == 200) {
// Password berhasil diganti
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Password berhasil diganti')),
);
} else {
// Terjadi kesalahan, tampilkan pesan kesalahan
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal mengganti password')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Password'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
controller: _oldPasswordController,
decoration: InputDecoration(
labelText: 'Old Password',
suffixIcon: IconButton(
icon: Icon(
_isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
},
)),
obscureText: !_isPasswordVisible,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
TextFormField(
controller: _newPasswordController,
decoration: InputDecoration(
labelText: 'New Password',
suffixIcon: IconButton(
icon: Icon(
_isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
},
)),
obscureText: !_isPasswordVisible,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
// TextFormField(
// controller: _confirmPasswordController,
// decoration: InputDecoration(labelText: 'Confirm New Password'),
// obscureText: true,
// ),
SizedBox(height: 20),
ElevatedButton(
onPressed: _changePassword,
child: Text('Change Password'),
),
],
),
),
);
}
}
This diff is collapsed.
// edit_profile_page.dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class EditProfilePage extends StatelessWidget {
late TextEditingController _emailController = TextEditingController();
late TextEditingController _firstNameController = TextEditingController();
late TextEditingController _lastNameController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Profile'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
),
TextFormField(
controller: _firstNameController,
decoration: InputDecoration(labelText: 'First Name'),
),
TextFormField(
controller: _lastNameController,
decoration: InputDecoration(labelText: 'Last Name'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => _updateProfile(context),
child: Text('Save'),
),
],
),
),
);
}
Future<void> _updateProfile(BuildContext context) async {
final updatedData = {
'email': _emailController.text,
'first_name': _firstNameController.text,
'last_name': _lastNameController.text,
};
SharedPreferences prefs = await SharedPreferences.getInstance();
String accessToken = prefs.getString('access_token') ?? '';
final response = await http.put(
Uri.parse(
'http://10.0.2.2:8000/api/change-profile/'), // Sesuaikan dengan endpoint API Anda
body: updatedData,
headers: {
"Authorization": "Bearer $accessToken"
}, // Tambahkan header autentikasi
);
if (response.statusCode == 200) {
// Perubahan profil berhasil
// Tambahkan logika penanganan berhasil di sini
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Profile updated successfully'),
duration: Duration(seconds: 2),
),
);
} else {
// Perubahan profil gagal
// Tambahkan logika penanganan kesalahan di sini
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to update profile'),
duration: Duration(seconds: 2),
),
);
}
}
}
import 'package:belajar_flutter/page/librarianlogin_page.dart';
import 'package:belajar_flutter/page/memberlogin_page.dart';
import 'package:belajar_flutter/services/auth_service.dart';
import 'package:flutter/material.dart';
import 'package:belajar_flutter/page/login_page.dart';
import 'package:belajar_flutter/page/register_page.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
actions: [
PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
child: ListTile(
title: Text('Login as Librarian'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LibrarianLoginPage(),
),
);
},
),
),
];
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Welcome to Library App!',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MemberLoginPage(),
),
);
},
child: Text('Login'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RegisterPage(),
),
);
},
child: Text('Register'),
),
],
),
),
);
}
}
This diff is collapsed.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class LibrarianLoginPage extends StatefulWidget {
@override
_LibrarianLoginPageState createState() => _LibrarianLoginPageState();
}
class _LibrarianLoginPageState extends State<LibrarianLoginPage> {
TextEditingController _usernameController = TextEditingController();
TextEditingController _passwordController = TextEditingController();
bool _isPasswordVisible = false;
Future<void> loginUser(BuildContext context) async {
final String username = _usernameController.text;
final String password = _passwordController.text;
final Map<String, dynamic> data = {
'username': username,
'password': password,
};
final http.Response response = await http.post(
Uri.parse("http://10.0.2.2:8000/api/librarian-login/"),
body: data,
);
if (response.statusCode == 200) {
final Map<String, dynamic> responseData = json.decode(response.body);
final String token = responseData['access'];
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('access_token', token);
Navigator.pushReplacementNamed(context, '/librarian_dashboard');
} else {
_showErrorSnackbar(context, "Login gagal. Silakan coba lagi.");
}
}
void _showErrorSnackbar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: Duration(seconds: 3),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Librarian Login'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your username';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(
_isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
},
)),
obscureText: !_isPasswordVisible,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => loginUser(context),
child: Text('Login'),
),
],
),
),
);
}
}
import 'package:belajar_flutter/page/dashboard_page.dart';
import 'package:belajar_flutter/services/auth_service.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import '../services/api_service.dart';
import '../utils/share_preferences.dart';
import 'register_page.dart';
class MemberLoginPage extends StatefulWidget {
@override
_MemberLoginPageState createState() => _MemberLoginPageState();
}
class _MemberLoginPageState extends State<MemberLoginPage> {
TextEditingController _usernameController = TextEditingController();
TextEditingController _passwordController = TextEditingController();
bool _isPasswordVisible = false;
// ApiService _apiService = ApiService();
Future<void> loginUser(BuildContext context) async {
final String username = _usernameController.text;
final String password = _passwordController.text;
final Map<String, dynamic> data = {
'username': username,
'password': password,
};
final http.Response response = await http.post(
Uri.parse("http://10.0.2.2:8000/api/member-login/"),
body: data,
);
// Memeriksa status response
if (response.statusCode == 200) {
// Login berhasil, simpan token
final Map<String, dynamic> responseData = json.decode(response.body);
final String token = responseData['access'];
print('Token: $token');
// Simpan token ke SharedPreferences
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('access_token', token);
// Navigasi ke halaman home
Navigator.pushReplacementNamed(context, '/dashboard');
} else {
_showErrorSnackbar(context, "Login gagal. Silakan coba lagi.");
}
}
void _showErrorSnackbar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: Duration(seconds: 3),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your username';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(
_isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
},
)),
obscureText: !_isPasswordVisible,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => loginUser(context),
child: Text('Login'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RegisterPage(),
),
);
},
child: Text('Register'),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class NearOutstandingBookLoanPage extends StatefulWidget {
@override
_NearOutstandingBookLoanPageState createState() =>
_NearOutstandingBookLoanPageState();
}
class _NearOutstandingBookLoanPageState
extends State<NearOutstandingBookLoanPage> {
List<dynamic> _nearOutstandingBookLoans = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
_fetchNearOutstandingBookLoans();
}
Future<void> _fetchNearOutstandingBookLoans() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String accessToken = prefs.getString('access_token') ?? '';
if (accessToken == null) {
// Token tidak tersedia, lakukan penanganan sesuai kebutuhan aplikasi Anda
return;
}
final url = Uri.parse('http://10.0.2.2:8000/api/near-outstanding-books/');
final response = await http.get(
url,
headers: <String, String>{
'Authorization': 'Bearer $accessToken',
},
);
if (response.statusCode == 200) {
setState(() {
_nearOutstandingBookLoans = json.decode(response.body);
_isLoading = false;
});
} else {
// Handle error
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Near Outstanding Book Loans'),
),
body: _isLoading
? Center(child: CircularProgressIndicator())
: _nearOutstandingBookLoans.isEmpty
? Center(child: Text('No near outstanding book loans found'))
: ListView.builder(
itemCount: _nearOutstandingBookLoans.length,
itemBuilder: (context, index) {
final loan = _nearOutstandingBookLoans[index];
return ListTile(
title: Text(loan['book_title']),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Loan Date: ${loan['loan_date']}'),
Text('Due Date: ${loan['due_date']}'),
Text('Member: ${loan['member_name']}'),
Text('Days Left: ${loan['days_left']}'),
],
),
leading: loan['book_image_url'] != null
? Image.network(
loan['book_image_url'],
width: 50,
height: 50,
fit: BoxFit.cover,
)
: Container(
// Gunakan Container untuk menetapkan ukuran Placeholder
width: 50,
height: 50,
child: Placeholder(),
),
);
},
),
);
}
}
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class OverdueBookLoanPage extends StatefulWidget {
@override
_OverdueBookLoanPageState createState() => _OverdueBookLoanPageState();
}
class _OverdueBookLoanPageState extends State<OverdueBookLoanPage> {
List<dynamic> _overdueBookLoans = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
_fetchOverdueBookLoans();
}
Future<void> _fetchOverdueBookLoans() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String accessToken = prefs.getString('access_token') ?? '';
if (accessToken == null) {
// Token tidak tersedia, lakukan penanganan sesuai kebutuhan aplikasi Anda
return;
}
final url = Uri.parse('http://10.0.2.2:8000/api/overdue-books/');
final response = await http.get(
url,
headers: <String, String>{
'Authorization': 'Bearer $accessToken',
},
);
if (response.statusCode == 200) {
setState(() {
_overdueBookLoans = json.decode(response.body);
_isLoading = false;
});
} else {
// Handle error
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Overdue Book Loans'),
),
body: _isLoading
? Center(child: CircularProgressIndicator())
: _overdueBookLoans.isEmpty
? Center(child: Text('No overdue book loans found'))
: ListView.builder(
itemCount: _overdueBookLoans.length,
itemBuilder: (context, index) {
final loan = _overdueBookLoans[index];
return ListTile(
title: Text(loan['book_title']),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Loan Date: ${loan['loan_date']}'),
Text('Due Date: ${loan['due_date']}'),
Text('Member: ${loan['member_name']}'),
Text('Overdue Days: ${loan['days_over']}'),
],
),
leading: loan['book_image_url'] != null
? Image.network(
loan['book_image_url'],
width: 50,
height: 50,
fit: BoxFit.cover,
)
: Container(
// Gunakan Container untuk menetapkan ukuran Placeholder
width: 50,
height: 50,
child: Placeholder(),
),
);
},
),
);
}
}
import 'package:flutter/material.dart';
import 'home_page.dart';
import 'package:http/http.dart' as http;
class RegisterPage extends StatefulWidget {
@override
_RegisterPageState createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
bool _isPasswordVisible = false;
Future<void> _regist() async {
if (_formKey.currentState?.validate() ?? false) {
final String apiUrl = 'http://10.0.2.2:8000/api/register-member/';
final response = await http.post(
Uri.parse(apiUrl),
body: {
'username': _usernameController.text,
'email': _emailController.text,
'password': _passwordController.text,
},
);
if (response.statusCode == 201) {
// ignore: use_build_context_synchronously
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
} else {
// Jika respons tidak sukses, tampilkan pesan kesalahan
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Error'),
content: Text('Registration failed'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Register'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your username';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
return null;
},
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(
_isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
},
)),
obscureText: !_isPasswordVisible,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _regist,
child: Text('Register'),
),
],
),
),
),
);
}
}
// import 'dart:convert';
// import 'package:http/http.dart' as http;
// import '../utils/share_preferences.dart';
// class ApiService {
// static const String baseUrl = 'http://10.0.2.2:8000';
// Future<Map<String, dynamic>> memberlogin(
// String username, String password) async {
// const String apiUrl =
// '$baseUrl/api/member-login/'; // Sesuaikan dengan endpoint login Anda
// try {
// final response = await http.post(
// Uri.parse(apiUrl),
// headers: <String, String>{
// 'Content-Type': 'application/json; charset=UTF-8',
// },
// body: jsonEncode(
// <String, String>{
// 'username': username,
// 'password': password,
// },
// ),
// );
// if (response.statusCode == 200) {
// // Parsing dan mengembalikan data dari respons API
// Map<String, dynamic> data = jsonDecode(response.body);
// final String accessToken = data['your_received_access_token'];
// await SharedPreferencesUtils.saveAccessToken(accessToken);
// return data;
// } else {
// // Handling jika login gagal
// return {'error': 'Login failed'};
// }
// } catch (error) {
// // Handling jika terjadi kesalahan selama panggilan API
// return {'error': 'An error occurred during the API call'};
// }
// }
// Future<Map<String, dynamic>> librarianlogin(
// String username, String password) async {
// const String apiUrl =
// '$baseUrl/api/librarian-login'; // Sesuaikan dengan endpoint login Anda
// try {
// final response = await http.post(
// Uri.parse(apiUrl),
// headers: <String, String>{
// 'Content-Type': 'application/json; charset=UTF-8',
// },
// body: jsonEncode(
// <String, String>{
// 'username': username,
// 'password': password,
// },
// ),
// );
// if (response.statusCode == 200) {
// // Parsing dan mengembalikan data dari respons API
// Map<String, dynamic> data = jsonDecode(response.body);
// return data;
// } else {
// // Handling jika login gagal
// return {'error': 'Login failed'};
// }
// } catch (error) {
// // Handling jika terjadi kesalahan selama panggilan API
// return {'error': 'An error occurred during the API call'};
// }
// }
// Future<String?> getAccessToken() async {
// return SharedPreferencesUtils.getAccessToken();
// }
// Future<void> saveAccessToken(String token) async {
// return SharedPreferencesUtils.saveAccessToken(token);
// }
// Future<void> removeAccessToken() async {
// return SharedPreferencesUtils.removeAccessToken();
// }
// }
// // Map<String, dynamic> _handleResponse(http.Response response) {
// // if (response.statusCode == 200) {
// // return jsonDecode(response.body);
// // } else {
// // throw Exception('Failed to load data');
// // }
// // }
\ No newline at end of file
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/share_preferences.dart';
enum UserRole { member, librarian }
class AuthenticationService {
static const String apiUrl =
'http://10.0.2.2:8000/api/member-login/'; // Replace with your API endpoint
static Future<String?> login(String username, String password) async {
try {
final response = await http.post(
Uri.parse(apiUrl),
body: {'username': username, 'password': password},
);
if (response.statusCode == 200) {
final Map<String, dynamic> data = json.decode(response.body);
final String accessToken = data['your_received_access_token'];
await SharedPreferencesUtils.saveAccessToken(accessToken);
return accessToken;
} else {
throw Exception('Authentication failed');
}
} catch (e) {
print('Error during login: $e');
throw Exception('Failed to connect to the server');
}
}
static Future<void> logout() async {
await SharedPreferencesUtils.removeAccessToken();
}
static Future<bool> isAuthenticated() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('access_token') != null;
}
static Future<UserRole?> getUserRole() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? token = prefs.getString('access_token');
// Jika token tidak ada, kembalikan null
if (token == null) return null;
if (token.contains('member')) {
return UserRole.member;
} else if (token.contains('librarian')) {
return UserRole.librarian;
}
return null;
}
}
class AuthenticationException implements Exception {
final String message;
AuthenticationException(this.message);
}
// book_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../model/book_model.dart';
import '../model/category_model.dart';
class BookService {
static const String baseUrl = "http://10.0.2.2:8000/api";
static Future<List<Book>> getBooks(String accessToken) async {
final response = await http.get(
Uri.parse("$baseUrl/auth/book/"),
headers: {"Authorization": "Bearer $accessToken"},
);
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body);
List<Book> books = data.map((item) => Book.fromJson(item)).toList();
return books;
} else {
throw Exception('Failed to load categories');
}
}
// static Future<Map<String, dynamic>> getCategories(String accessToken) async {
// final response = await http.get(
// Uri.parse("$baseUrl/category/"),
// headers: {"Authorization": "Bearer $accessToken"},
// );
// if (response.statusCode == 200) {
// final Map<String, dynamic> data = json.decode(response.body);
// return data;
// } else {
// throw Exception('Failed to load books');
// }
// }
// static Future<Map<String, dynamic>> getBookByCategory(
// String category, String accessToken) async {
// final response = await http.get(
// Uri.parse("$baseUrl/book/by-category/$category"),
// headers: {"Authorization": "Bearer $accessToken"},
// );
// if (response.statusCode == 200) {
// final Map<String, dynamic> data = json.decode(response.body);
// return data;
// } else {
// throw Exception('Failed to load books');
// }
// }
// // Future<List<Category>> getCategories() async {
// // final response = await http.get(Uri.parse("$baseUrl/category"), headers: {
// // 'Authorization': 'Bearer
// // });
// // if (response.statusCode == 200) {
// // List<dynamic> data = json.decode(response.body);
// // List<Category> categories =
// // data.map((json) => Category.fromJson(json)).toList();
// // return categories;
// // } else {
// // throw Exception('Failed to load categories');
// // }
// // }
// }
// // class BookService {
// // static const String apiUrl = 'http://10.0.2.2:8000/api';
// // static Future<dynamic> getBooks() async {
// // SharedPreferences prefs = await SharedPreferences.getInstance();
// // final String accessToken = prefs.getString('accessToken') ?? '';
// // final Map<String, String> headers = {
// // 'Authorization': 'Bearer $accessToken',
// // };
// // final response = await http.get(
// // Uri.parse('$apiUrl/book'),
// // headers: headers,
// // );
// // if (response.statusCode == 200) {
// // final List<dynamic> jsonData = json.decode(response.body)['results'];
// // return jsonData.map((data) => Book.fromJson(data)).toList();
// // } else {
// // print('Failed to load data: ${response.statusCode}');
// // }
// // }
// // static Future<List<Book>> getBooksByCategory(String category) async {
// // SharedPreferences prefs = await SharedPreferences.getInstance();
// // final String accessToken = prefs.getString('accessToken') ?? '';
// // final Map<String, String> headers = {
// // 'Authorization': 'Bearer $accessToken',
// // };
// // final response = await http.get(
// // Uri.parse('$apiUrl/by-category/$category/'),
// // headers: headers,
// // );
// // if (response.statusCode == 200) {
// // final List<dynamic> jsonData = json.decode(response.body)['results'];
// // return jsonData.map((data) => Book.fromJson(data)).toList();
// // } else {
// // throw Exception('Failed to load data: ${response.statusCode}');
// // }
// // }
// // static Future<List<Category>> getCategories() async {
// // SharedPreferences prefs = await SharedPreferences.getInstance();
// // final String accessToken = prefs.getString('accessToken') ?? '';
// // final Map<String, String> headers = {
// // 'Authorization': 'Bearer $accessToken',
// // };
// // final response = await http.get(
// // Uri.parse('$apiUrl/category/'),
// // headers: headers,
// // );
// // if (response.statusCode == 200) {
// // final List<dynamic> jsonData = json.decode(response.body)['results'];
// // return jsonData.map((data) => Category.fromJson(data)).toList();
// // } else {
// // throw Exception('Failed to load categories: ${response.statusCode}');
// // }
// // }
}
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../model/category_model.dart';
class BookService {
static const String baseUrl = "http://10.0.2.2:8000/api";
static Future<List<Category>> getCategories() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? accessToken = prefs.getString('access_token');
final response = await http.get(
Uri.parse("http://10.0.2.2:8000/category/"),
headers: {"Authorization": "Bearer $accessToken"},
);
if (response.statusCode == 200) {
final List<dynamic> responseData = json.decode(response.body);
return responseData.map((data) => Category.fromJson(data)).toList();
} else {
throw Exception('Failed to load categories');
}
}
}
import 'dart:convert';
import 'package:http/http.dart' as http;
class UserService {
final String baseUrl;
final String authToken;
UserService({required this.baseUrl, required this.authToken});
Future<void> updateUserProfile(String userId, String newEmail,
String newFirstName, String newLastName) async {
try {
final response = await http.put(
Uri.parse('$baseUrl/users/$userId'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer $authToken', // Header autentikasi
},
body: jsonEncode(<String, String>{
'email': newEmail,
'first_name': newFirstName,
'last_name': newLastName,
}),
);
if (response.statusCode == 200) {
// Perubahan berhasil disimpan
print('User profile updated successfully.');
} else {
// Gagal menyimpan perubahan
print('Failed to update user profile.');
throw Exception('Failed to update user profile.');
}
} catch (e) {
print('Error: $e');
throw Exception('Error occurred while updating user profile.');
}
}
}
import 'package:flutter/material.dart';
/// Implementation copied from [RouteObserver] to notifies [TransitionRouteAware]s of changes to the
/// state of their [Route], plus when the route transition finished
class TransitionRouteObserver<R extends TransitionRoute<dynamic>?>
extends NavigatorObserver {
final Map<R, Set<TransitionRouteAware>> _listeners =
<R, Set<TransitionRouteAware>>{};
/// Subscribe [routeAware] to be informed about changes to [route].
///
/// Going forward, [routeAware] will be informed about qualifying changes
/// to [route], e.g. when [route] is covered by another route or when [route]
/// is popped off the [Navigator] stack.
void subscribe(TransitionRouteAware routeAware, R route) {
assert(route != null);
final subscribers =
_listeners.putIfAbsent(route, () => <TransitionRouteAware>{});
if (subscribers.add(routeAware)) {
routeAware.didPush();
Future.delayed(route!.transitionDuration, () {
routeAware.didPushAfterTransition();
});
}
}
/// Unsubscribe [routeAware].
///
/// [routeAware] is no longer informed about changes to its route. If the given argument was
/// subscribed to multiple types, this will unregister it (once) from each type.
void unsubscribe(TransitionRouteAware routeAware) {
for (final route in _listeners.keys) {
final subscribers = _listeners[route];
subscribers?.remove(routeAware);
}
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
if (route is R && previousRoute is R) {
final previousSubscribers = _listeners[previousRoute]?.toList();
if (previousSubscribers != null) {
for (final routeAware in previousSubscribers) {
routeAware.didPopNext();
}
}
final subscribers = _listeners[route as R]?.toList();
if (subscribers != null) {
for (final routeAware in subscribers) {
routeAware.didPop();
}
}
}
}
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
if (route is R && previousRoute is R) {
final previousSubscribers = _listeners[previousRoute];
if (previousSubscribers != null) {
for (final routeAware in previousSubscribers) {
routeAware.didPushNext();
}
}
}
}
}
/// An interface for objects that are aware of their current [TransitionRoute].
///
/// This is used with [TransitionRouteObserver] to make a widget aware of changes to the
/// [Navigator]'s session history.
mixin TransitionRouteAware {
/// Called when the top route has been popped off, and the current route
/// shows up.
void didPopNext() {}
/// Called when the current route has been pushed.
void didPush() {}
/// Called when the current route has been pushed and finished transition.
void didPushAfterTransition() {}
/// Called when the current route has been popped off.
void didPop() {}
/// Called when a new route has been pushed, and the current route is no
/// longer visible.
void didPushNext() {}
}
const mockUsers = {
'dribbble@gmail.com': '12345',
'hunter@gmail.com': 'hunter',
'near.huscarl@gmail.com': 'subscribe to pewdiepie',
'@.com': '.',
};
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesUtils {
// Method to save the access token
static Future<void> saveAccessToken(String token) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('accessToken', token);
}
// Method to get the access token
static Future<String?> getAccessToken() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('accessToken');
}
// Method to remove the access token
static Future<void> removeAccessToken() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove('accessToken');
}
}
// token_provider.dart
import 'package:flutter/material.dart';
class TokenProvider with ChangeNotifier {
String? _accessToken;
String? get accessToken => _accessToken;
setAccessToken(String? token) {
_accessToken = token;
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class AnimatedNumericText extends StatelessWidget {
AnimatedNumericText({
super.key,
required this.initialValue,
required this.targetValue,
required this.controller,
this.curve = Curves.linear,
this.formatter = '#,##0.00',
this.style,
}) : numberFormat = NumberFormat(formatter),
numberAnimation = Tween<double>(
begin: initialValue,
end: targetValue,
).animate(
CurvedAnimation(
parent: controller,
curve: curve,
),
);
final double initialValue;
final double targetValue;
final AnimationController controller;
final Curve curve;
final String formatter;
final TextStyle? style;
final NumberFormat numberFormat;
final Animation<double> numberAnimation;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: numberAnimation,
builder: (context, child) => Text(
numberFormat.format(numberAnimation.value),
style: style,
),
);
}
}
import 'package:flutter/material.dart';
import '../model/book_model.dart';
class BookItem extends StatelessWidget {
final Book book;
const BookItem({required this.book});
@override
Widget build(BuildContext context) {
return ListTile(
leading: Image.network(book.image),
title: Text(book.title),
subtitle: Text(book.category.name),
onTap: () {
Navigator.pushNamed(context, '/book-detail', arguments: {'book': book});
},
);
}
}
import 'package:flutter/material.dart';
enum FadeDirection {
startToEnd,
endToStart,
topToBottom,
bottomToTop,
}
class FadeIn extends StatefulWidget {
const FadeIn({
super.key,
this.fadeDirection = FadeDirection.startToEnd,
this.offset = 1.0,
this.controller,
this.duration,
this.curve = Curves.easeOut,
required this.child,
}) : assert(
controller == null && duration != null ||
controller != null && duration == null,
),
assert(offset > 0);
/// [FadeIn] animation can be controlled via external [controller]. If
/// [controller] is not provided, it will use the default internal controller
/// which will run the animation in initState()
final AnimationController? controller;
final FadeDirection fadeDirection;
final double offset;
final Widget child;
final Duration? duration;
final Curve curve;
@override
FadeInState createState() => FadeInState();
}
class FadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
AnimationController? _controller;
late Animation<Offset> _slideAnimation;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
if (widget.controller == null) {
_controller = AnimationController(
vsync: this,
duration: widget.duration,
);
}
_updateAnimations();
_controller?.forward();
}
void _updateAnimations() {
Offset? begin;
Offset? end;
final offset = widget.offset;
switch (widget.fadeDirection) {
case FadeDirection.startToEnd:
begin = Offset(-offset, 0);
end = Offset.zero;
case FadeDirection.endToStart:
begin = Offset(offset, 0);
end = Offset.zero;
case FadeDirection.topToBottom:
begin = Offset(0, -offset);
end = Offset.zero;
case FadeDirection.bottomToTop:
begin = Offset(0, offset);
end = Offset.zero;
}
_slideAnimation = Tween<Offset>(
begin: begin,
end: end,
).animate(
CurvedAnimation(
parent: _effectiveController!,
curve: widget.curve,
),
);
_opacityAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: _effectiveController!,
curve: widget.curve,
),
);
}
AnimationController? get _effectiveController =>
widget.controller ?? _controller;
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: _slideAnimation,
child: FadeTransition(
opacity: _opacityAnimation,
child: widget.child,
),
);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment