Skip to content

Commit

Permalink
release: 1.0.0 🎉 (#50)
Browse files Browse the repository at this point in the history
* fix: prepare for 0.2.1 (#48)

* fix(functions): stats contacts regression

* fix: measurement creation for new contacts

* fix: no decimals in lower android APIs

* refactor: homepage landscape mode

* chore: ui clean up

* fix: material color

* chore: update TASKS

* release: prepare for 1.0 (#49)

* chore: base template setup

* feat: initial proposal for rate limit template

* feat: first draft access denied template

* chore(models): account status

* chore: rework templates

* refactor(redux): account status

* refactor(home): with status templates

* feat: implement premium accounts

* feat: implement notice

* chore: update notice on move to premium

* feat: added common settings

* chore: code clean up

* chore: update TASKS

* release: pre 1.0.0 ✨

* chore(functions): default account data

* chore: code clean up

* chore: added get_version

* feat: had to bump to 1.0.1

* feat: ok, so no more 1.0.1

still on the road map to 1.0.0

* chore: update README
  • Loading branch information
jogboms authored Jul 17, 2018
1 parent 0724a8f commit d6f79fa
Show file tree
Hide file tree
Showing 36 changed files with 744 additions and 184 deletions.
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
<img src="./assets/images/logo.png" style="margin: auto" width="90" />

# TailorMade
# TailorMade: Managing a Fashion designer's daily routine.

A Flutter experiment for managing a Fashion designer's daily routine. Logo, Design & Concept by Me.
TailorMade is what actually started out as an experiment with [Flutter](https://flutter.io/), [flutter_redux](https://github.com/brianegan/flutter_redux) and [Firebase Cloud Functions](https://github.com/flutter/plugins/tree/master/packages/cloud_functions) but instead turned out to be a valuable tool for managing a Fashion designer's daily routine. It is clean, easy on the eyes and overall has a very smooth feel. It also handles offline use cases with Firebase Cloud. Logo, Design & Concept by Me.

> Android-only support
<div>
<a href='https://play.google.com/store/apps/details?id=io.github.jogboms.tailormade'><img alt='Get it on Google Play' src='./screenshots/google_play.png' height='40px'/></a>
</div>

## Tools

1. Firebase Auth
2. Firebase Cloud Firestore
3. Firebase Cloud Functions
4. Firebase Storage
5. RxDart
6. Redux
7. Redux Epics
5. Google SignIn
6. RxDart
7. Flutter Redux
8. Redux Epics
9. Image Picker
10. Photo View
11. Cached Network Image
12. Flutter SpinKit
13. Flutter Get Version
14. Flutter Masked Text

For a full description of OSS used, see pubspec.yaml

Expand Down
20 changes: 13 additions & 7 deletions TASKS.todo
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
PENDING:
☐ delete-able payment
☐ delete-able images
☐ implement share payment + image
☐ filter + search jobs & contacts
☐ create a new contact from phone contact
☐ Collect account details on account creation
☐ Generate invoice & share as SMS containing link to pay on paystack
☐ Firebase function on successful payments w/ push notification
☐ delete-able images
☐ implement share payment + image
☐ filter + search jobs & contacts
☐ create a new contact from phone contact @high
☐ Collect account details on account creation
☐ Generate invoice & share as SMS containing link to pay on paystack
☐ Firebase function on successful payments w/ push notification
☐ On account create, send personal email + email to user @high

COMPLETED:
✔ Notice modal + indicator on home page @done(18-07-17 11:54)
✔ add rate limit danger + access blocked screens @critical @done(18-07-17 10:31)
✔ landscape scale on homepage @low @done(18-07-16 11:56)
✔ no decimals in measurements @critical @done(18-07-16 10:28)
✔ fix measurement creation on new contact @critical @done(18-07-16 10:20)
✔ remodel w/ authID @done(18-07-15 14:43)
✔ scope storage path to accounts @high @done(18-07-13 16:19)
✔ update stats db on creation @high @done(18-07-10 16:39)
Expand Down
1 change: 1 addition & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
/captures
GeneratedPluginRegistrant.java
google-services.json
key.properties
51 changes: 46 additions & 5 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ if (flutterRoot == null) {
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

ext.versionMajor = 1
ext.versionMinor = 0
ext.versionPatch = 0
ext.versionClassifier = null
ext.isSnapshot = false
ext.minimumSdkVersion = 16

android {
compileSdkVersion 27

Expand All @@ -25,16 +36,28 @@ android {
applicationId "io.github.jogboms.tailormade"
minSdkVersion 16
targetSdkVersion 27
versionCode 1
versionName "1.0"
versionCode generateVersionCode()
versionName generateVersionName()
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}

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
signingConfig signingConfigs.release

// minifyEnabled true
// useProguard true

// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

Expand All @@ -52,6 +75,24 @@ android {
}
}

private Integer generateVersionCode() {
return ext.versionMajor * 10000 + ext.versionMinor * 100 + ext.versionPatch
}

private String generateVersionName() {
String versionName = "${ext.versionMajor}.${ext.versionMinor}.${ext.versionPatch}"
if (ext.versionClassifier == null) {
if (ext.isSnapshot) {
ext.versionClassifier = "SNAPSHOT"
}
}

if (ext.versionClassifier != null) {
versionName += "-" + ext.versionClassifier
}
return versionName;
}

flutter {
source '../..'
}
Expand Down
6 changes: 5 additions & 1 deletion functions/functions/src/fn/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export function _onAuth(onCreate: ChangeState) {
email: user.email,
displayName: user.displayName,
phoneNumber: user.phoneNumber,
photoURL: user.photoURL
photoURL: user.photoURL,
status: 0,
hasPremiumEnabled: false,
notice: "Its amazing to have you here, look around, Enjoy the experience!",
hasReadNotice: false,
});

const stats = db.collection("stats").doc(user.uid);
Expand Down
4 changes: 2 additions & 2 deletions functions/functions/src/fn/stats_contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EventContext, firestore } from "firebase-functions";
import { isArray } from "util";
import { ChangeState } from "../utils";

export function _onStatsContacts(onCreate: ChangeState) {
export function _onStatsContacts(state: ChangeState) {
return async (
snapshot: firestore.DocumentSnapshot,
context: EventContext
Expand All @@ -20,7 +20,7 @@ export function _onStatsContacts(onCreate: ChangeState) {

return stats.update({
contacts: {
total: total + (onCreate ? count : -count)
total: total + (state === ChangeState.Created ? count : -count)
}
});
};
Expand Down
1 change: 1 addition & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class TMApp extends StatelessWidget {
debugShowCheckedModeBanner: false,
title: TMStrings.appName,
theme: new ThemeData(
primaryColor: Colors.white,
accentColor: kAccentColor,
primarySwatch: kPrimarySwatch,
fontFamily: TMFonts.raleway,
Expand Down
42 changes: 42 additions & 0 deletions lib/models/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:tailor_made/models/main.dart';

enum AccountModelStatus { enabled, disabled, warning, pending }

class AccountModel extends Model {
String uid;
String storeName;
String email;
String displayName;
int phoneNumber;
String photoURL;
AccountModelStatus status;
bool hasPremiumEnabled;
String notice;
bool hasReadNotice;

AccountModel({
@required this.uid,
Expand All @@ -17,6 +23,10 @@ class AccountModel extends Model {
@required this.displayName,
@required this.phoneNumber,
@required this.photoURL,
@required this.status,
@required this.hasPremiumEnabled,
@required this.notice,
@required this.hasReadNotice,
});

factory AccountModel.fromJson(Map<String, dynamic> json) {
Expand All @@ -28,13 +38,41 @@ class AccountModel extends Model {
displayName: json['displayName'],
phoneNumber: int.tryParse(json['phoneNumber'].toString()),
photoURL: json['photoURL'],
status: AccountModelStatus.values[int.tryParse(json['status'].toString())],
hasPremiumEnabled: json['hasPremiumEnabled'],
notice: json['notice'],
hasReadNotice: json['hasReadNotice'],
);
}

factory AccountModel.fromDoc(DocumentSnapshot doc) {
return AccountModel.fromJson(doc.data)..reference = doc.reference;
}

AccountModel copyWith({
String storeName,
String displayName,
int phoneNumber,
String photoURL,
AccountModelStatus status,
bool hasPremiumEnabled,
String notice,
bool hasReadNotice,
}) {
return new AccountModel(
uid: this.uid,
storeName: storeName ?? this.storeName,
email: this.email,
displayName: displayName ?? this.displayName,
phoneNumber: phoneNumber ?? this.phoneNumber,
photoURL: photoURL ?? this.photoURL,
status: status ?? this.status,
hasPremiumEnabled: hasPremiumEnabled ?? this.hasPremiumEnabled,
notice: notice ?? this.notice,
hasReadNotice: hasReadNotice ?? this.hasReadNotice,
);
}

toMap() {
return {
"uid": uid,
Expand All @@ -43,6 +81,10 @@ class AccountModel extends Model {
"displayName": displayName,
"phoneNumber": phoneNumber,
"photoURL": photoURL,
"status": status.index,
"hasPremiumEnabled": hasPremiumEnabled,
"notice": notice,
"hasReadNotice": hasReadNotice,
};
}
}
14 changes: 14 additions & 0 deletions lib/models/settings.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class SettingsModel {
String premiumNotice;

SettingsModel({
this.premiumNotice,
});

factory SettingsModel.fromJson(Map<String, dynamic> json) {
assert(json != null);
return new SettingsModel(
premiumNotice: json['premiumNotice'],
);
}
}
6 changes: 3 additions & 3 deletions lib/pages/contacts/contact.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ class _ContactState extends State<ContactPage> {
child: TabBar(
labelStyle: ralewayMedium(14.0),
tabs: [
Tab(child: Text(TABS[0])),
Tab(child: Text(TABS[1])),
Tab(child: Text(TABS[2])),
Tab(child: Text(TABS[0], style: TextStyle(color: Colors.white))),
Tab(child: Text(TABS[1], style: TextStyle(color: Colors.white))),
Tab(child: Text(TABS[2], style: TextStyle(color: Colors.white))),
],
),
),
Expand Down
5 changes: 4 additions & 1 deletion lib/pages/contacts/ui/contact_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ class ContactFormState extends State<ContactForm> {

Widget _buildForm() {
return Theme(
data: ThemeData(hintColor: Colors.grey.shade400),
data: ThemeData(
hintColor: kBorderSideColor,
primaryColor: kPrimaryColor,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Form(
Expand Down
7 changes: 5 additions & 2 deletions lib/pages/contacts/ui/contact_measure.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ContactMeasure extends StatefulWidget {

ContactMeasure({
Key key,
this.contact,
@required this.contact,
}) : super(key: key);

@override
Expand Down Expand Up @@ -112,7 +112,10 @@ class _ContactMeasureState extends State<ContactMeasure> with SnackBarProvider {
showLoadingSnackBar();

try {
await widget.contact.reference.updateData(widget.contact.toMap());
// During contact creation
if (widget.contact.reference != null) {
await widget.contact.reference.updateData(widget.contact.toMap());
}
closeLoadingSnackBar();
showInSnackBar("Successfully Updated");
} catch (e) {
Expand Down
15 changes: 15 additions & 0 deletions lib/pages/homepage/home_view_model.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:tailor_made/models/account.dart';
import 'package:tailor_made/models/contact.dart';
import 'package:tailor_made/redux/actions/account.dart';
import 'package:tailor_made/redux/states/account.dart';
import 'package:tailor_made/redux/states/contacts.dart';
import 'package:tailor_made/redux/states/stats.dart';
Expand All @@ -17,4 +18,18 @@ class HomeViewModel extends StatsViewModel {
this.store.state.contacts.status == ContactsStatus.loading ||
this.store.state.account.status == AccountStatus.loading;
}

bool get hasSkipedPremium => store.state.account.hasSkipedPremium == true;

bool get isDisabled => account.status == AccountModelStatus.disabled;

bool get isWarning => account.status == AccountModelStatus.warning;

bool get isPending => account.status == AccountModelStatus.pending;

onPremiumSignUp() => this.store.dispatch(OnPremiumSignUp(payload: account));

onReadNotice() => this.store.dispatch(OnReadNotice(payload: account));

onSkipedPremium() => this.store.dispatch(OnSkipedPremium());
}
Loading

0 comments on commit d6f79fa

Please sign in to comment.