diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html
index 1137c203ece..d24a4559a5d 100644
--- a/client/src/app/header/header.component.html
+++ b/client/src/app/header/header.component.html
@@ -1,3 +1,13 @@
+
+
Open in the application?
+
+
Open
+
+
+
+
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index b2951c28711..2926648a520 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -3,6 +3,27 @@
@use '_mixins' as *;
@use '_button-mixins' as *;
+.mobile-msg {
+ display: flex;
+ align-items: center;
+ padding: 0 1.5rem;
+ height: $header-mobile-msg-height;
+ background: pvar(--bg-secondary-400);
+
+ .msg {
+ color: pvar(--fg-300);
+ font-weight: $font-bold;
+
+ @include font-size(14px);
+ }
+
+ button {
+ my-global-icon {
+ color: pvar(--fg-200);
+ }
+ }
+}
+
.root {
--co-logo-size: 34px;
--co-root-padding: 1.5rem;
diff --git a/client/src/app/header/header.component.ts b/client/src/app/header/header.component.ts
index c8760c19f28..657306da933 100644
--- a/client/src/app/header/header.component.ts
+++ b/client/src/app/header/header.component.ts
@@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common'
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
-import { Router, RouterLink } from '@angular/router'
+import { ActivatedRoute, NavigationEnd, Router, RouterLink } from '@angular/router'
import {
AuthService,
AuthStatus,
@@ -25,6 +25,8 @@ import { Subscription } from 'rxjs'
import { GlobalIconComponent } from '../shared/shared-icons/global-icon.component'
import { ButtonComponent } from '../shared/shared-main/buttons/button.component'
import { SearchTypeaheadComponent } from './search-typeahead.component'
+import { isAndroid, isIOS, isIphone } from '@root-helpers/web-browser'
+import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@Component({
selector: 'my-header',
@@ -51,6 +53,8 @@ import { SearchTypeaheadComponent } from './search-typeahead.component'
})
export class HeaderComponent implements OnInit, OnDestroy {
+ private static LS_HIDE_MOBILE_MSG = 'hide-mobile-msg'
+
@ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent
@ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent
@ViewChild('dropdown') dropdown: NgbDropdown
@@ -62,6 +66,9 @@ export class HeaderComponent implements OnInit, OnDestroy {
currentInterfaceLanguage: string
+ mobileMsg = false
+ mobileAppUrl = ''
+
private serverConfig: ServerConfig
private quickSettingsModalSub: Subscription
@@ -76,6 +83,7 @@ export class HeaderComponent implements OnInit, OnDestroy {
private screenService: ScreenService,
private modalService: PeertubeModalService,
private router: Router,
+ private route: ActivatedRoute,
private menu: MenuService
) { }
@@ -127,6 +135,8 @@ export class HeaderComponent implements OnInit, OnDestroy {
this.quickSettingsModalSub = this.modalService.openQuickSettingsSubject
.subscribe(() => this.openQuickSettings())
+
+ this.setupMobileMsg()
}
ngOnDestroy () {
@@ -147,6 +157,58 @@ export class HeaderComponent implements OnInit, OnDestroy {
// ---------------------------------------------------------------------------
+ private setupMobileMsg () {
+ if (!this.isInMobileView()) return
+ if (peertubeLocalStorage.getItem(HeaderComponent.LS_HIDE_MOBILE_MSG) === 'true') return
+ if (!isAndroid()) return
+
+ this.mobileMsg = true
+ document.body.classList.add('mobile-app-msg')
+
+ const host = window.location.host
+
+ const getVideoId = (url: string) => {
+ const matches = url.match(/^\/w\/([^/]+)$/)
+
+ if (matches) return matches[1]
+ }
+
+ const getChannelId = (url: string) => {
+ const matches = url.match(/^\/c\/([^/]+)/)
+
+ if (matches) return matches[1]
+ }
+
+ this.router.events.subscribe(event => {
+ if (!(event instanceof NavigationEnd)) return
+
+ const url = event.url
+
+ const videoId = getVideoId(url)
+ if (videoId) {
+ this.mobileAppUrl = `peertube:///video/${videoId}?host=${host}`
+ return
+ }
+
+ const channelId = getChannelId(url)
+ if (channelId) {
+ this.mobileAppUrl = `peertube:///video-channel/${channelId}?host=${host}`
+ return
+ }
+
+ this.mobileAppUrl = `peertube:///?host=${host}`
+ })
+ }
+
+ hideMobileMsg () {
+ this.mobileMsg = false
+ document.body.classList.remove('mobile-app-msg')
+
+ peertubeLocalStorage.setItem(HeaderComponent.LS_HIDE_MOBILE_MSG, 'true')
+ }
+
+ // ---------------------------------------------------------------------------
+
isRegistrationAllowed () {
if (!this.serverConfig) return false
diff --git a/client/src/root-helpers/web-browser.ts b/client/src/root-helpers/web-browser.ts
index 9dade20e8e0..b3193766117 100644
--- a/client/src/root-helpers/web-browser.ts
+++ b/client/src/root-helpers/web-browser.ts
@@ -1,4 +1,4 @@
-function isIOS () {
+export function isIOS () {
if (/iPad|iPhone|iPod/.test(navigator.platform)) {
return true
}
@@ -9,16 +9,19 @@ function isIOS () {
navigator.platform.includes('MacIntel'))
}
-function isSafari () {
+export function isSafari () {
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
}
-function isMobile () {
+export function isMobile () {
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
}
-export {
- isIOS,
- isSafari,
- isMobile
+export function isIphone () {
+ return /iPhone/i.test(navigator.userAgent)
}
+
+export function isAndroid () {
+ return /Android/i.test(navigator.userAgent)
+}
+
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 7b3d04e2984..ea352614533 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -85,15 +85,27 @@ body {
--header-height: #{$header-height};
+ &.mobile-app-msg {
+ --header-height: #{$header-height + $header-mobile-msg-height};
+ }
+
--menu-fg: #{pvar(--fg-400)};
--menu-bg: #{pvar(--bg-secondary-400)};
@media screen and (max-width: $small-view) {
--header-height: #{$header-height-small-view};
+
+ &.mobile-app-msg {
+ --header-height: #{$header-height-small-view + $header-mobile-msg-height};
+ }
}
@media screen and (max-width: $mobile-view) {
--header-height: #{$header-height-mobile-view};
+
+ &.mobile-app-msg {
+ --header-height: #{$header-height-mobile-view + $header-mobile-msg-height};
+ }
}
// Light theme
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss
index 6baf1a0c1b5..30b294ee84a 100644
--- a/client/src/sass/include/_variables.scss
+++ b/client/src/sass/include/_variables.scss
@@ -23,14 +23,13 @@ $button-font-size: 1rem;
$header-height: 94px;
$header-height-small-view: 74px;
$header-height-mobile-view: 124px;
+$header-mobile-msg-height: 48px;
$menu-width: 248px;
$menu-collapsed-width: 50px;
$menu-margin-left: 2rem;
$menu-overlay-view: 1200px;
-$sub-menu-height: 81px;
-
$banner-inverted-ratio: math.div(1, 6);
$max-channels-width: 1200px;