Skip to content
This repository was archived by the owner on Aug 4, 2023. It is now read-only.

Commit fc70cc2

Browse files
committed
Merge branch 'master' into tgpetrov/examples-dataform
2 parents 36549fd + 7fe7319 commit fc70cc2

36 files changed

+390
-19
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ hooks/
1414
*.apk
1515
*.ipa
1616

17-
Telerik.keystore
17+
Telerik.keystore
18+
19+
GoogleService-Info.plist
20+
google-services.json

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
// Place your settings in this file to overwrite default and user settings.
22
{
3+
"typescript.tsdk": "./node_modules/typescript/lib"
34
}

FIREBASE.ms

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Updating the news in firebase manually:
2+
Update the firebase-database.json with the added articles and "import" in firebase.
3+
4+
Sending push notifications from the firebase console:
5+
Make sure to set:
6+
- Message text: the message text
7+
- Message label: message title displayed in firebase
8+
In the Advanced:
9+
- Title: the message title (this is important!)
10+
- In custom data add the fields:
11+
- inAppTitle: repeat the "Title"
12+
- inAppBody: repeat the "Message text"
13+
- url: link to the page to be opened on tap (best matched with the news article)
14+
- id: the id of the news
15+
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
1.15 KB
Loading
1.45 KB
Loading
1.85 KB
Loading
571 Bytes
Loading
825 Bytes
Loading
1.02 KB
Loading

app/common/firebase.ts

Lines changed: 154 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,138 @@ import * as utils from "utils/utils";
33
import * as dialogs from "ui/dialogs";
44
import {isAndroid, isIOS} from "platform";
55
import * as settings from "application-settings";
6+
import { Observable } from "data/observable";
7+
import * as navigator from "../common/navigator";
68

7-
const enabled = false;
9+
const enabled = true;
10+
11+
interface FirebaseArticle {
12+
readonly id: string;
13+
readonly title: string;
14+
readonly body: string;
15+
readonly url: string;
16+
readonly date: string;
17+
}
18+
19+
const FIREBASE_NEWS_INITAL = "firebase:news:inital";
20+
const FIREBASE_NEWS_READ_KEY = "firebase:news:read";
21+
let _readArticles;
22+
function loadReadArticles() {
23+
if (_readArticles) {
24+
return _readArticles;
25+
}
26+
27+
const str = settings.getString(FIREBASE_NEWS_READ_KEY, "{}");
28+
try {
29+
_readArticles = JSON.parse(str);
30+
} catch(e) {
31+
console.log("Failed to parse read firebase articles.");
32+
_readArticles = {};
33+
}
34+
35+
return _readArticles;
36+
}
37+
38+
function saveReadArticles() {
39+
try {
40+
settings.setString(FIREBASE_NEWS_READ_KEY, JSON.stringify(loadReadArticles()));
41+
} catch(e) {
42+
console.log("Failed to persist read articles: " + e);
43+
}
44+
}
45+
46+
function getIsArticleRead(id: string): boolean {
47+
return !!loadReadArticles()[id];
48+
}
49+
50+
function markAsRead(id: string) {
51+
loadReadArticles()[id] = true;
52+
saveReadArticles();
53+
}
54+
function markAllAsRead() {
55+
let readArticles = loadReadArticles();
56+
viewModel.news.filter(n => n && n.id).forEach(n => readArticles[n.id] = true);
57+
console.log("Maked as read: " + JSON.stringify(readArticles));
58+
saveReadArticles();
59+
viewModel.updateHasUnreadNews();
60+
}
61+
function markAsUnread(id: string) {
62+
loadReadArticles()[id] = false;
63+
saveReadArticles();
64+
}
65+
function markAllAsReadOnFirstRun() {
66+
const isInitalRun = settings.getBoolean(FIREBASE_NEWS_INITAL, true);
67+
if (isInitalRun) {
68+
markAllAsRead();
69+
settings.setBoolean(FIREBASE_NEWS_INITAL, false);
70+
}
71+
}
72+
73+
export class Article extends Observable {
74+
readonly id: string;
75+
readonly title: string;
76+
readonly body: string;
77+
readonly url: string;
78+
readonly date: string;
79+
80+
constructor(base: FirebaseArticle) {
81+
super();
82+
this.id = base.id;
83+
this.title = base.title;
84+
this.body = base.body;
85+
this.url = base.url;
86+
this.date = base.date;
87+
}
88+
89+
get isRead(): boolean {
90+
return !!(this.id && getIsArticleRead(this.id));
91+
}
92+
set isRead(value: boolean) {
93+
if (value && this.id) {
94+
markAsRead(this.id);
95+
} else {
96+
markAsUnread(this.id);
97+
}
98+
this.notifyPropertyChange("isRead", value);
99+
viewModel.updateHasUnreadNews();
100+
}
101+
}
102+
103+
export class ViewModel extends Observable {
104+
news: Article[];
105+
hasNews: boolean = false;
106+
hasUnreadNews: boolean = false;
107+
108+
constructor() {
109+
super();
110+
this.updateNews([]);
111+
}
112+
113+
updateNews(updates: FirebaseArticle[]) {
114+
this.news = updates.filter(fb => !!fb).map(fb => new Article(fb));
115+
this.updateHasNews();
116+
this.updateHasUnreadNews();
117+
this.notifyPropertyChange("news", this.news);
118+
}
119+
120+
updateHasNews() {
121+
let newValue = this.news && this.news.length > 0;
122+
if (newValue != this.hasNews) {
123+
this.hasNews = newValue;
124+
this.notifyPropertyChange("hasNews", newValue);
125+
}
126+
}
127+
128+
updateHasUnreadNews() {
129+
let newValue = this.news && this.news.length > 0 && this.news.some(args => !args.isRead);
130+
if (newValue != this.hasUnreadNews) {
131+
this.hasUnreadNews = newValue;
132+
this.notifyPropertyChange("hasUnreadNews", newValue);
133+
console.log("hasUnreadNews: " + this.hasUnreadNews);
134+
}
135+
}
136+
}
137+
export const viewModel = new ViewModel();
8138

9139
var firebase;
10140
var lastHandledData;
@@ -42,26 +172,33 @@ function firebaseInit() {
42172
console.log("Firebase init!!!");
43173

44174
firebase.init({
175+
persist: true,
45176
onMessageReceivedCallback(message) {
46177
console.log("Got message!");
47178
console.log(JSON.stringify(message));
48179

49180
let url = (<any>message).url;
181+
let id = (<any>message).id;
50182
if (url) {
51183
if (message.foreground) {
52184
dialogs.confirm({
53185
title: (<any>message).inAppTitle,
54186
message: (<any>message).inAppBody,
55-
okButtonText: "Open",
56-
cancelButtonText: "Close"
187+
okButtonText: isAndroid ? "OPEN" : "Open",
188+
cancelButtonText: isAndroid ? "OK" : "OK"
57189
}).then(result => {
58190
if (result) {
59191
utils.openUrl(url);
192+
if (id) {
193+
markAsRead(id);
194+
viewModel.news.filter(a => a.id === a.id).forEach(a => a.isRead = true);
195+
}
196+
navigator.navigateToWhatIsNew();
60197
}
61198
});
62199
} else {
63200
if (lastHandledData != url) {
64-
utils.openUrl(url);
201+
navigator.navigateToWhatIsNew();
65202
lastHandledData = url;
66203
}
67204
}
@@ -73,7 +210,19 @@ function firebaseInit() {
73210
settings.setBoolean("user-granted-push", true);
74211
}
75212
}).then(value => {
76-
console.log("Firebase init done!");
213+
firebase.addValueEventListener((result) => {
214+
if (!result.error) {
215+
console.log("Update news: " + JSON.stringify(result.value))
216+
viewModel.updateNews(result.value || []);
217+
markAllAsReadOnFirstRun();
218+
} else {
219+
console.log(JSON.stringify(result));
220+
}
221+
}, "/news").then((listenerWrapper) => {
222+
const path = listenerWrapper.path;
223+
const listeners = listenerWrapper.listeners
224+
console.log("Listening for firebase data. path: " + path + ", listeners: " + listeners);
225+
});
77226
}).catch(e => {
78227
console.log("Failed to init firebase. " + e);
79228
console.log("stack:\n" + e.stack);

app/common/navigator.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ export function navigateToAbout() {
124124
}
125125
}
126126

127+
export function navigateToWhatIsNew() {
128+
var topmost = frame.topmost();
129+
if (topmost.currentEntry.moduleName !== "views/what-is-new") {
130+
frame.topmost().navigate(traceNavigateTo("views/what-is-new"));
131+
}
132+
}
133+
127134
export function navigateBack() {
128135
frame.goBack();
129136
}

app/examples/listview/selection/selection-view-model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ export class SelectionViewModel extends Observable {
208208
if (this.isSelectionActive === true || this.isReorderActive === true) {
209209
return;
210210
}
211-
this.CurrentItem = (<ObservableArray<BlogPostItemData>>this._owner.items).getItem(args.itemIndex);
211+
this.CurrentItem = (<any>this._owner.items).getItem(args.itemIndex);
212212
this._currentItemIndex = args.itemIndex;
213213
topmostFrame().navigate({
214214
moduleName: "examples/listview/selection/detail-page",

app/view-models/main-page-view-model.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import examplesVM = require("./examples-model");
22
import observable = require("data/observable");
33
import paltfrom = require("platform");
4+
import { viewModel } from "../common/firebase";
45

56
export class MainPageViewModel extends observable.Observable {
7+
8+
get firebase() {
9+
return viewModel;
10+
}
11+
612
get exampleGroups(): Array<examplesVM.ExampleGroup> {
713
return examplesVM.groups;
814
}

app/views/main-page/main-page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { LayoutBase } from "ui/layouts/layout-base";
1919
import { RadSideDrawer } from "nativescript-telerik-ui-pro/sidedrawer";
2020
import {onAfterIntro} from "../../common/firebase";
2121

22-
export function pageLoaded(args){
22+
export function pageLoaded(args) {
2323
prof.stop("main-page");
2424
let page = <pages.Page>(<View>args.object).page;
2525
setTimeout(() => (<any>page).canEnter = true, 3500);

app/views/main-page/main-page.xml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,13 @@
2121
<sdp:DrawerPage.actionBar>
2222
<ActionBar automationText="ActionBar" android:opacity="0">
2323
<android>
24-
<NavigationButton icon="res://ic_menu_main" tap="showSlideout" automationText="SidebarMenu" />
24+
<NavigationButton icon="{{ firebase.hasUnreadNews, firebase.hasUnreadNews ? 'res://ic_menu_main_new' : 'res://ic_menu_main' }}" tap="showSlideout" automationText="SidebarMenu" />
2525
</android>
26-
<iOS>
27-
<NavigationButton icon="res://ic_back" />
28-
</iOS>
2926
<ActionBar.actionItems>
3027
<iOS>
3128
<ActionItem id="actionbar-menu" ios.position="left" tap="showSlideout" automationText="SidebarMenu">
3229
<ActionItem.actionView>
33-
<Image src="res://ic_menu_main" width="22" height="22" margin="0, 8, 0, -8" />
30+
<Image src="{{ firebase.hasUnreadNews, firebase.hasUnreadNews ? 'res://ic_menu_main_new' : 'res://ic_menu_main' }}" width="22" height="22" margin="0, 8, 0, -8" />
3431
</ActionItem.actionView>
3532
</ActionItem>
3633
</iOS>

app/views/side-drawer-content/side-drawer-content-common.css

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@
2222
border-color: #3D5AFE;
2323
}
2424

25-
.drawer-external-link {
25+
.drawer-external-link,
26+
.drawer-news-link {
2627
height: 44;
2728
}
28-
.drawer-external-link:highlighted {
29+
30+
.drawer-external-link:highlighted,
31+
.drawer-news-link:highlighted {
2932
background-color: #516BFE;
3033
}
3134
.drawer-external-icon {
@@ -35,6 +38,14 @@
3538
horizontal-align: center;
3639
padding: 0 36;
3740
}
41+
.drawer-news-icon {
42+
background-position: 0% 50%;
43+
background-repeat: no-repeat;
44+
horizontal-align: center;
45+
padding: 0 36;
46+
}
47+
48+
.drawer-news-label,
3849
.drawer-external-label {
3950
font-size: 13;
4051
color: white;

app/views/side-drawer-content/side-drawer-content.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@ import { GroupPageViewModel } from "../../view-models/group-page-view-model"
33
import * as navigator from "../../common/navigator"
44
import * as gestures from "ui/gestures";
55
import { groups } from "../../view-models/examples-model"
6+
import * as firebase from "../../common/firebase";
67
import { topmost } from "ui/frame"
78
import { grayTouch } from "../../common/effects";
89
import * as application from "application";
10+
import * as applicationSettings from "application-settings";
11+
import { Observable } from "data/observable";
12+
13+
class SidedrawerViewModel extends Observable {
14+
public groups = groups;
15+
public firebase = firebase.viewModel;
16+
}
917

1018
export function onLoaded(args) {
11-
args.object.bindingContext = groups;
19+
args.object.bindingContext = new SidedrawerViewModel();
1220
}
1321

1422
export function tileTouch(args: gestures.TouchGestureEventData) {
@@ -58,6 +66,15 @@ export function tapAbout(args) {
5866
}
5967
}
6068

69+
export function tapWhatIsNew() {
70+
closeDrawer();
71+
if (application.android) {
72+
setTimeout(() => navigator.navigateToWhatIsNew(), 600);
73+
} else {
74+
navigator.navigateToWhatIsNew();
75+
}
76+
}
77+
6178
export function tapDrawerLink(args) {
6279
closeDrawer();
6380
navigator.openLink(args.object);

app/views/side-drawer-content/side-drawer-content.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
<ScrollView class="drawer-content">
2-
<StackLayout loaded="onLoaded">
2+
<StackLayout loaded="onLoaded" unloaded="onUnloaded">
33
<Image src="res://logo_main_nav" margin="40 0 24 0" horizontalAlignment="center" stretch="none" />
44

55
<Button text="Home" tap="tapHome" class="drawer-button" />
66
<Button text="About" tap="tapAbout" class="drawer-button" />
77

8+
<GridLayout class="drawer-news-link" tap="tapWhatIsNew">
9+
<GridLayout class="drawer-news-icon">
10+
<Image src="res://ic_notification" opacity="{{ firebase.hasUnreadNews, (firebase && firebase.hasUnreadNews) ? 1 : 0 }}" width="10" height="10" horizontalAlignment="left" />
11+
<Label text="What's new" class="drawer-news-label" margin="0 18" />
12+
</GridLayout>
13+
</GridLayout>
14+
815
<GridLayout class="drawer-external-link" tap="tapDrawerLink" tag="http://docs.nativescript.org/getting-started">
916
<GridLayout class="drawer-external-icon">
1017
<Label text="Getting started" class="drawer-external-label" />

0 commit comments

Comments
 (0)