Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a35489d1c5 | ||
|
|
3d584bcf3f | ||
|
|
61417c57d7 | ||
|
|
eab026c89c | ||
|
|
9a91d994b7 | ||
|
|
c0079f3cac | ||
|
|
4fc7a2e0bb | ||
|
|
9b00955345 | ||
|
|
1b2ad33926 | ||
|
|
9731e7039c | ||
|
|
b14757b120 | ||
|
|
2317e856bf | ||
|
|
32f18c3d3c | ||
|
|
e99f6de434 | ||
|
|
bb3b3061c6 | ||
|
|
0067468d57 | ||
|
|
b2cbed444c | ||
|
|
1d0186a12e | ||
|
|
c0e4653129 | ||
|
|
60f18fc6fb | ||
|
|
b531dadf96 | ||
|
|
2b0ea01710 | ||
|
|
9f5e7ce3f9 |
@@ -2,7 +2,9 @@
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="80"/>](https://play.google.com/store/apps/details?id=im.status.ethereum)
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80"/>](https://f-droid.org/packages/im.status.ethereum/)
|
||||
[<img src="doc/github_badge.png" alt="Get it on Github" height="80"/>](https://github.com/status-im/status-mobile/releases)
|
||||
|
||||
|
||||
[Get it on Github](https://github.com/status-im/status-mobile/releases)
|
||||
|
||||
Join us in creating a browser, messenger, and gateway to a decentralized world. Status is a free (libre) open source mobile client targeting Android & iOS built entirely on [Ethereum](https://ethereum.org/) technologies. That's right, no middle men and `go-ethereum` running directly on your device.
|
||||
|
||||
@@ -20,31 +22,31 @@ If this interests you, **help us make Status a reality** - anyone can contribute
|
||||
|
||||
## How to Contribute?
|
||||
|
||||
Go straight to the [docs](https://status.im/docs) or [join our chat](https://join.status.im/chat/public/status) and choose what interests you:
|
||||
Go straight to the [docs](doc/) and choose what interests you:
|
||||
|
||||
- **Developer** Developers are the heart of software and to keep Status beating
|
||||
we need all the help we can get! If you're looking to code in ClojureScript or
|
||||
Golang then Status is the project for you! We use React Native and there is even
|
||||
some Java/Objective-C too! Want to learn more about it? Start by reading our
|
||||
[Developer Introduction](https://status.im/developer_tools/) which guides you
|
||||
[Developer Introduction](doc/starting-guide.md) which guides you
|
||||
through the technology stack and start browsing [beginner
|
||||
issues](https://github.com/status-im/status-mobile/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20).
|
||||
Then you can read how to [Build
|
||||
Status](https://status.im/technical/build_status/), which talks about managing
|
||||
project dependencies, coding guidelines and testing procedures. The [doc/](doc/)
|
||||
directory also has valuable information for contributors.
|
||||
Status](doc/starting-guide.md), which talks about managing
|
||||
project dependencies, coding guidelines and testing procedures.
|
||||
Check out our [coding guidelines](doc/new-guidelines.md).
|
||||
|
||||
- **Community Management**
|
||||
Metcalfe's law states that the value of a network is proportional to the square of the number of connected users of the system - without community Status is meaningless. We're looking to create a positive, fun environment to explore new ideas, experiment and grow the Status community. Building a community takes a lot of work but the people you'll meet and long lasting relationships you form will be well worth it, check out our [Mission and Community Principles](https://status.im/about)
|
||||
|
||||
- **Specification / Documentation**
|
||||
John Dewey once said "Education is not preparation for life; education is life *itself* ". Developers & Designers need guidance and it all starts from documentation and specifications. Our software is only as good as its documentation, head over to our [docs](https://status.im/docs) and see how you can improve what we have.
|
||||
John Dewey once said "Education is not preparation for life; education is life *itself* ". Developers & Designers need guidance and it all starts from documentation and specifications. Our software is only as good as its documentation, check out our [docs](doc/) and see how you can improve what we have.
|
||||
|
||||
- **Blog Writing**
|
||||
Content is King, keeping our blog up to date and informing the community of news helps keep everyone on the same page. [Jump into our chat](https://join.status.im/chat/public/status) and discuss with the team how you can contribute!
|
||||
Content is King, keeping our blog up to date and informing the community of news helps keep everyone on the same page.
|
||||
|
||||
- **Testers**
|
||||
It's bug hunting season! Status is currently under active development and there is sure to be a bunch of learning, [build status from scratch](https://status.im/technical/build_status/) or if an android user check out our [nightly builds](https://status.im/nightly). You can shake your phone to submit bug reports, or start browsing our [Github Issues](https://github.com/status-im/status-mobile/issues). Every bug you find brings Status closer to stable, usable software for everyone to enjoy!
|
||||
It's bug hunting season! Status is currently under active development and there is sure to be a bunch of learning, [build status from scratch](doc/starting-guide.md) or if an android user check out our [nightly builds](https://status.im/nightly). You can shake your phone to submit bug reports, or start browsing our [Github Issues](https://github.com/status-im/status-mobile/issues). Every bug you find brings Status closer to stable, usable software for everyone to enjoy!
|
||||
|
||||
- **Security**
|
||||
Status is a visual interface to make permanent changes on the Blockchain, it handles crypto-tokens that have real value and allows 3rd party code execution. Security is paramount to its success. You are given permission to break Status as hard as you can, as long as you share your findings with the community!
|
||||
@@ -52,9 +54,6 @@ Status is a visual interface to make permanent changes on the Blockchain, it han
|
||||
- **Evangelism**
|
||||
Help us spread the word! Tell a friend *right now*, in fact tell **everyone** - yell from a mountain if you have to, every person counts! If you've got a great story to tell or have some interesting way you've spread the word about Status let us know about it in our [chat](https://join.status.im/chat/public/status)
|
||||
|
||||
## Status API
|
||||
View our [API Docs](https://status.im/developer_tools/status_web_api.html) and learn how to integrate your DApp into Status. You can read more about how to add your DApp to Status [here](https://status.im/developer_tools/add_your_dapp.html).
|
||||
|
||||
## Give me Binaries!
|
||||
|
||||
You can get our Beta builds for both Android and iOS on our [website](https://status.im), through our [nightly builds](https://status.im/nightly/) or by [building it yourself](https://status.im/technical/build_status/).
|
||||
@@ -68,12 +67,8 @@ Without the dedication of these outstanding individuals, Status would not exist.
|
||||
|
||||
## Contact us
|
||||
|
||||
Feel free to email us at [support@status.im](mailto:support@status.im) or better yet, [join our chat](https://join.status.im/chat/public/status).
|
||||
Feel free to email us at [support@status.im](mailto:support@status.im).
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [Mozilla Public License v2.0](https://github.com/status-im/status-mobile/blob/develop/LICENSE.md)
|
||||
|
||||
## Testing Supported by
|
||||
|
||||
[](https://www.browserstack.com/automate/public-build/SFBWQ3k5R2tnT2t6UGsxU1h1U0g3VFlCNnoxVTRHYWNoTDhsb1laaTJZWT0tLUZMUm5sSVFBSU9qSWM1RUJvK1I3Z3c9PQ==--ad15ae7992b432b33743749d40318952415bd98f)
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# Git Etiquette Guide
|
||||
|
||||
To ensure smooth collaboration, we've outlined the following Git etiquette guide. This guide will help in writing concise commit messages, managing git branches, pull request descriptions, and other best practices.
|
||||
|
||||
## 1. Commit Messages
|
||||
|
||||
Your commit message should provide clear context about the change. This allows others (and future you) to understand why a particular change was made.
|
||||
|
||||
Here are the guidelines to write concise and meaningful commit messages:
|
||||
|
||||
- **Use the Imperative Mood**: Commit messages should be written in the present tense and imperative mood ("Add feature" not "Added feature" or "Adds feature").
|
||||
- **First Line Should Be Summary**: The first line of your commit message should be a brief summary of the changes, kept under 50 characters whenever possible.
|
||||
- **Body**: Follow this summary line with a blank line, then a more detailed explanation is always helpful but not necessary. This is often useful when the commit has significant importance or complexity.
|
||||
- **Consider Including the 'Why'**: If the commit resolves an issue or bug, mention the issue number and provide a brief explanation of how the changes fix the issue.
|
||||
|
||||
Here's a good commit message example:
|
||||
|
||||
```
|
||||
Fix off-by-one error in loop (issue #123)
|
||||
|
||||
This commit fixes an off-by-one error in the list iteration function.
|
||||
This was causing an index out of bounds exception when processing the last item in the list.
|
||||
Tests have been updated to catch this in the future.
|
||||
```
|
||||
|
||||
## 2. Branch Management
|
||||
|
||||
Git branches should be concise and focused on a single feature, fix, or improvement. Here are a few practices:
|
||||
|
||||
- **Branch Name**: The branch name should be descriptive of the change and follow a specific format such as `type/description`. Example: `feature/login-system`.
|
||||
- **Please don't accept branch recommendation by Github UI**: The branch name recommendation by Github UI will in most cases be a very long branch name concatenated with the issue number, which often results in branch names like this one :
|
||||
`feature/Merge_yada_and_yada_yada_into_one_yada_yada_status-lol_#199021`
|
||||
- **Keep Branches Short-lived**: The longer a branch lives separate from the `main` or `master` branch, the higher the possibility of merge conflicts. Aim to merge branches as soon as the work is complete and has been tested.
|
||||
- **Rebase Frequently**: If your branch lives longer, rebase it frequently on top of the main branch.
|
||||
|
||||
## 3. Pull Requests
|
||||
|
||||
Pull requests (PRs) are how your changes get incorporated into the project. Here are a few key points:
|
||||
|
||||
- **Link PR to an Issue**: Your PR should link to the issue it resolves. This helps track the resolution of bugs or addition of features.
|
||||
- **PR Description**: The PR description should outline what changes have been made and why. This should be a summary of your commits.
|
||||
- **Small and Focused PRs**: Aim to keep PRs small and focused. It's easier to understand and review a PR with a handful of related changes than one with multiple unrelated changes.
|
||||
|
||||
Example PR description:
|
||||
|
||||
```
|
||||
## Description
|
||||
This PR implements a login system (issue #456). It adds a login and logout button, as well as an authentication system. This will allow users to create an account and sign in.
|
||||
|
||||
## Changes
|
||||
- Add login button component
|
||||
- Add logout button component
|
||||
- Add authentication API integration
|
||||
|
||||
## Issue Related
|
||||
This PR closes #456
|
||||
```
|
||||
|
||||
## 4. Other Best Practices
|
||||
|
||||
- **Stay Updated**: Keep your local copy up to date with the upstream regularly.
|
||||
- **Test Before Pushing**: Always test your code and make sure all tests pass before you push.
|
||||
|
||||
Following these guidelines ensures a smoother collaboration and maintains a clean, manageable project history. Thanks for your contributions!
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[quo2.components.community.style :as style]
|
||||
[quo2.components.counter.counter :as counter]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.theme :as theme]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.community.icon :as community-icon]
|
||||
@@ -21,18 +22,65 @@
|
||||
:resize-mode :center
|
||||
:size 20
|
||||
:color (colors/theme-colors
|
||||
colors/neutral-40
|
||||
colors/neutral-50)}]
|
||||
colors/neutral-50
|
||||
colors/neutral-40)}]
|
||||
(pos? unread-mentions-count)
|
||||
[counter/counter {:type :default} unread-mentions-count]
|
||||
|
||||
unread-messages?
|
||||
[unread-grey-dot :unviewed-messages-public]))
|
||||
|
||||
(defn communities-membership-list-item
|
||||
(defn communities-list-view-item
|
||||
[props
|
||||
{:keys [name
|
||||
muted?
|
||||
locked?
|
||||
status
|
||||
muted
|
||||
unread-messages?
|
||||
unread-mentions-count
|
||||
community-icon
|
||||
tokens]}]
|
||||
[rn/view
|
||||
{:style (merge (style/community-card 16)
|
||||
{:margin-bottom 12})}
|
||||
[rn/touchable-highlight
|
||||
(merge {:style {:height 56
|
||||
:border-radius 16}}
|
||||
props)
|
||||
[rn/view {:style style/detail-container}
|
||||
[rn/view (style/list-info-container)
|
||||
[community-icon/community-icon
|
||||
{:images community-icon} 32]
|
||||
[rn/view
|
||||
{:flex 1
|
||||
:margin-horizontal 12}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:accessibility-label :community-name-text
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail
|
||||
:style {:color (when muted
|
||||
(colors/theme-colors
|
||||
colors/neutral-40
|
||||
colors/neutral-60))}}
|
||||
name]
|
||||
[community-view/community-stats-column
|
||||
{:type :list-view}]]
|
||||
(if (= status :gated)
|
||||
[community-view/permission-tag-container
|
||||
{:locked? locked?
|
||||
:tokens tokens}]
|
||||
[notification-view
|
||||
{:muted? muted
|
||||
:unread-mentions-count unread-mentions-count
|
||||
:unread-messages? unread-messages?}])]]]])
|
||||
|
||||
(defn communities-membership-list-item
|
||||
[props
|
||||
bottom-sheet?
|
||||
{:keys [name
|
||||
muted
|
||||
unviewed-messages-count
|
||||
unviewed-mentions-count
|
||||
status
|
||||
@@ -59,17 +107,22 @@
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail
|
||||
:weight :semi-bold
|
||||
:size :paragraph-1}
|
||||
:size :paragraph-1
|
||||
:style (when muted
|
||||
{:color (if (theme/dark?)
|
||||
colors/neutral-60
|
||||
colors/neutral-40)})}
|
||||
name]]
|
||||
|
||||
[rn/view
|
||||
{:justify-content :center
|
||||
:margin-right 16}
|
||||
:margin-right (when bottom-sheet?
|
||||
16)}
|
||||
(if (= status :gated)
|
||||
[community-view/permission-tag-container
|
||||
{:locked? locked?
|
||||
:tokens tokens}]
|
||||
[notification-view
|
||||
{:muted? muted?
|
||||
{:muted? muted
|
||||
:unread-mentions-count unviewed-mentions-count
|
||||
:unread-messages? (pos? unviewed-messages-count)}])]]])
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(defn divider
|
||||
[]
|
||||
[theme]
|
||||
{:border-top-width 1
|
||||
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-90)
|
||||
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-90 theme)
|
||||
:margin-top 8
|
||||
:margin-bottom 7
|
||||
:align-items :center
|
||||
@@ -45,6 +45,6 @@
|
||||
:width 20})
|
||||
|
||||
(defn right-text
|
||||
[override-theme]
|
||||
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40 override-theme)
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)
|
||||
:margin-right 12})
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
(ns quo2.components.drawers.action-drawers.view
|
||||
(:require [quo2.components.icon :as icon]
|
||||
(:require [react-native.core :as rn]
|
||||
[quo2.components.drawers.action-drawers.style :as style]
|
||||
[quo2.components.icon :as icon]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[quo2.components.drawers.action-drawers.style :as style]))
|
||||
[quo2.theme :as quo.theme]))
|
||||
|
||||
(defn- get-icon-color
|
||||
[danger? override-theme]
|
||||
[danger? theme]
|
||||
(if danger?
|
||||
colors/danger-60
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 override-theme)))
|
||||
(colors/theme-colors colors/danger-50 colors/danger-60 theme)
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme)))
|
||||
|
||||
(defn- divider
|
||||
[]
|
||||
[theme]
|
||||
[rn/view
|
||||
{:style (style/divider)
|
||||
{:style (style/divider theme)
|
||||
:accessible true
|
||||
:accessibility-label :divider}])
|
||||
|
||||
@@ -24,7 +25,7 @@
|
||||
[rn/view (dissoc props :on-press) child]
|
||||
[rn/touchable-highlight props child]))
|
||||
|
||||
(defn- action
|
||||
(defn- action-internal
|
||||
[{:keys [icon
|
||||
label
|
||||
sub-label
|
||||
@@ -34,65 +35,66 @@
|
||||
disabled?
|
||||
on-press
|
||||
add-divider?
|
||||
override-theme
|
||||
theme
|
||||
accessibility-label
|
||||
icon-color]
|
||||
:as action-props}]
|
||||
(when action-props
|
||||
[:<> {:key label}
|
||||
(when add-divider?
|
||||
[divider])
|
||||
[maybe-pressable disabled?
|
||||
{:accessibility-label accessibility-label
|
||||
:style (style/container sub-label disabled?)
|
||||
:underlay-color (colors/theme-colors colors/neutral-5 colors/neutral-90 override-theme)
|
||||
:on-press on-press}
|
||||
[rn/view
|
||||
{:style (style/row-container sub-label)}
|
||||
[rn/view
|
||||
{:accessibility-label :left-icon-for-action
|
||||
:accessible true
|
||||
:style style/left-icon}
|
||||
[icon/icon icon
|
||||
{:color (or icon-color (get-icon-color danger? override-theme))
|
||||
:size 20}]]
|
||||
[rn/view
|
||||
{:style style/text-container}
|
||||
icon-color]}]
|
||||
[:<>
|
||||
(when add-divider?
|
||||
[divider theme])
|
||||
[maybe-pressable disabled?
|
||||
{:accessibility-label accessibility-label
|
||||
:style (style/container sub-label disabled?)
|
||||
:underlay-color (colors/theme-colors colors/neutral-5 colors/neutral-90 theme)
|
||||
:on-press on-press}
|
||||
[rn/view
|
||||
{:style (style/row-container sub-label)}
|
||||
[rn/view
|
||||
{:accessibility-label :left-icon-for-action
|
||||
:accessible true
|
||||
:style style/left-icon}
|
||||
[icon/icon icon
|
||||
{:color (or icon-color (get-icon-color danger? theme))
|
||||
:size 20}]]
|
||||
[rn/view
|
||||
{:style style/text-container}
|
||||
[text/text
|
||||
{:size :paragraph-1
|
||||
:weight :medium
|
||||
:style {:color
|
||||
(cond
|
||||
danger? (colors/theme-colors colors/danger-50 colors/danger-60 theme)
|
||||
:else (colors/theme-colors colors/neutral-100 colors/white theme))}}
|
||||
label]
|
||||
(when sub-label
|
||||
[text/text
|
||||
{:size :paragraph-1
|
||||
:weight :medium
|
||||
:style {:color
|
||||
(cond
|
||||
danger? (colors/theme-colors colors/danger-50 colors/danger-60 override-theme)
|
||||
:else (colors/theme-colors colors/neutral-100 colors/white override-theme))}}
|
||||
label]
|
||||
(when sub-label
|
||||
{:size :paragraph-2
|
||||
:style {:color
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
|
||||
sub-label])]
|
||||
(when (or right-text right-icon)
|
||||
[rn/view {:style style/right-side-container}
|
||||
(when right-text
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:style {:color
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 override-theme)}}
|
||||
sub-label])]
|
||||
(when (or right-text right-icon)
|
||||
[rn/view {:style style/right-side-container}
|
||||
(when right-text
|
||||
[text/text
|
||||
{:accessibility-label :right-text-for-action
|
||||
:size :paragraph-1
|
||||
:style (style/right-text override-theme)}
|
||||
right-text])
|
||||
(when right-icon
|
||||
[rn/view
|
||||
{:style style/right-icon
|
||||
:accessible true
|
||||
:accessibility-label :right-icon-for-action}
|
||||
[icon/icon right-icon
|
||||
{:color (get-icon-color danger? override-theme)
|
||||
:size 20}]])])]]]))
|
||||
{:accessibility-label :right-text-for-action
|
||||
:size :paragraph-1
|
||||
:style (style/right-text theme)}
|
||||
right-text])
|
||||
(when right-icon
|
||||
[rn/view
|
||||
{:style style/right-icon
|
||||
:accessible true
|
||||
:accessibility-label :right-icon-for-action}
|
||||
[icon/icon right-icon
|
||||
{:color (get-icon-color danger? theme)
|
||||
:size 20}]])])]]])
|
||||
|
||||
(def ^:private action (quo.theme/with-theme action-internal))
|
||||
|
||||
(defn action-drawer
|
||||
[sections]
|
||||
[:<>
|
||||
(doall
|
||||
(for [actions sections]
|
||||
(doall
|
||||
(map action actions))))])
|
||||
(let [filtered-actions (filter some? actions)]
|
||||
(doall
|
||||
(map #(with-meta [action %] {:key (:label %)}) filtered-actions)))))])
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
(defn themes
|
||||
[type]
|
||||
(case type
|
||||
:main {:icon-color (theme-colors colors/neutral-50 colors/neutral-10)
|
||||
:background (theme-colors colors/white colors/neutral-90)
|
||||
:main {:icon-color (theme-colors colors/neutral-50 colors/neutral-40)
|
||||
:background (theme-colors colors/white colors/neutral-95)
|
||||
:text-color (theme-colors colors/neutral-100 colors/white)}
|
||||
:danger {:icon-color (theme-colors colors/danger-50 colors/danger-60)
|
||||
:background (theme-colors colors/white colors/neutral-90)
|
||||
:background (theme-colors colors/white colors/neutral-95)
|
||||
:text-color (theme-colors colors/danger-50 colors/danger-60)}
|
||||
:transparent {:icon-color (theme-colors colors/neutral-50 colors/neutral-10)
|
||||
:text-color (theme-colors colors/neutral-100 colors/white)}))
|
||||
|
||||
@@ -54,7 +54,6 @@
|
||||
[user-avatar/user-avatar
|
||||
{:full-name name
|
||||
:profile-picture profile-picture
|
||||
:override-theme :dark
|
||||
:size :medium
|
||||
:status-indicator? false
|
||||
:customization-color customization-color
|
||||
@@ -66,7 +65,7 @@
|
||||
:size 32
|
||||
:blurred? true
|
||||
:labelled? true
|
||||
:resource :main-icons2/check
|
||||
:resource :i/check
|
||||
:accessibility-label :logged-in-tag
|
||||
:icon-color colors/success-50
|
||||
:override-theme :dark
|
||||
@@ -76,7 +75,6 @@
|
||||
{:size 32
|
||||
:type :blur-bg
|
||||
:icon true
|
||||
:override-theme :dark
|
||||
:style style/option-button
|
||||
:on-press on-options-press
|
||||
:accessibility-label :profile-card-options}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
(ns quo2.components.settings.category.component-spec
|
||||
(:require [test-helpers.component :as h]
|
||||
[quo2.components.settings.category.view :as category]))
|
||||
|
||||
(h/describe "category tests"
|
||||
(h/test "category label renders"
|
||||
(h/render [category/category
|
||||
{:label "label"
|
||||
:data [{:title "Item 1"
|
||||
:left-icon :i/browser
|
||||
:chevron? true}]}])
|
||||
(h/is-truthy (h/get-by-text "label")))
|
||||
|
||||
(h/test "category item renders"
|
||||
(h/render [category/category
|
||||
{:label "label"
|
||||
:data [{:title "Item 1"
|
||||
:left-icon :i/browser
|
||||
:chevron? true}]}])
|
||||
(h/is-truthy (h/get-by-text "Item 1"))))
|
||||
@@ -0,0 +1,44 @@
|
||||
(ns quo2.components.settings.category.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def container
|
||||
{:left 0
|
||||
:right 0
|
||||
:padding-horizontal 20
|
||||
:padding-top 12
|
||||
:padding-bottom 8})
|
||||
|
||||
(defn items
|
||||
[theme blur?]
|
||||
{:margin-top 12
|
||||
:border-radius 16
|
||||
:background-color (if blur?
|
||||
colors/white-opa-5
|
||||
(colors/theme-colors colors/white colors/neutral-95 theme))
|
||||
:border-width 1
|
||||
:border-color (if blur?
|
||||
colors/white-opa-5
|
||||
(colors/theme-colors colors/neutral-10 colors/neutral-80 theme))})
|
||||
|
||||
(defn separator
|
||||
[theme blur?]
|
||||
{:height 1
|
||||
:background-color (if blur?
|
||||
colors/white-opa-5
|
||||
(colors/theme-colors colors/neutral-10 colors/neutral-80 theme))})
|
||||
|
||||
(defn blur-container
|
||||
[]
|
||||
{:position :absolute
|
||||
:left 0
|
||||
:right 0
|
||||
:bottom 0
|
||||
:top 0
|
||||
:overflow :hidden})
|
||||
|
||||
(defn blur-view
|
||||
[]
|
||||
{:style {:flex 1}
|
||||
:blur-radius 10
|
||||
:blur-type (colors/theme-colors :light :dark)
|
||||
:blur-amount 20})
|
||||
@@ -0,0 +1,27 @@
|
||||
(ns quo2.components.settings.category.view
|
||||
(:require
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.settings.settings-list.view :as settings-list]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.blur :as blur]
|
||||
[react-native.core :as rn]
|
||||
[quo2.components.settings.category.style :as style]
|
||||
[quo2.theme :as quo.theme]))
|
||||
|
||||
(defn- category-internal
|
||||
[{:keys [label data blur? theme]}]
|
||||
[rn/view {:style style/container}
|
||||
(when blur?
|
||||
[rn/view (style/blur-container) [blur/view (style/blur-view)]])
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
|
||||
label]
|
||||
[rn/flat-list
|
||||
{:data data
|
||||
:style (style/items theme blur?)
|
||||
:render-fn (fn [item] [settings-list/settings-list item])
|
||||
:separator [rn/view {:style (style/separator theme blur?)}]}]])
|
||||
|
||||
(def category (quo.theme/with-theme category-internal))
|
||||
@@ -82,6 +82,7 @@
|
||||
quo2.components.settings.privacy-option
|
||||
quo2.components.settings.reorder-item.view
|
||||
quo2.components.settings.settings-list.view
|
||||
quo2.components.settings.category.view
|
||||
quo2.components.share.qr-code.view
|
||||
quo2.components.share.share-qr-code.view
|
||||
quo2.components.tabs.account-selector
|
||||
@@ -231,6 +232,7 @@
|
||||
(def account quo2.components.settings.accounts.view/account)
|
||||
(def settings-list quo2.components.settings.settings-list.view/settings-list)
|
||||
(def reorder-item quo2.components.settings.reorder-item.view/reorder-item)
|
||||
(def category quo2.components.settings.category.view/category)
|
||||
|
||||
;;;; SHARE
|
||||
(def qr-code quo2.components.share.qr-code.view/qr-code)
|
||||
|
||||
@@ -37,5 +37,6 @@
|
||||
[quo2.components.selectors.selectors.component-spec]
|
||||
[quo2.components.settings.reorder-item.component-spec]
|
||||
[quo2.components.settings.settings-list.component-spec]
|
||||
[quo2.components.settings.category.component-spec]
|
||||
[quo2.components.share.share-qr-code.component-spec]
|
||||
[quo2.components.tags.--tests--.status-tags-component-spec]))
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[clojure.string :as string]
|
||||
[clojure.walk :as walk]
|
||||
[quo.design-system.colors :as colors]
|
||||
[quo2.foundations.colors :as quo2.colors]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.async-storage.core :as async-storage]
|
||||
@@ -16,6 +17,7 @@
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.common.muting.helpers :refer [format-mute-till]]
|
||||
[status-im2.contexts.chat.events :as chat.events]))
|
||||
|
||||
(def crop-size 1000)
|
||||
@@ -72,7 +74,8 @@
|
||||
:isMember :is-member?
|
||||
:adminSettings :admin-settings
|
||||
:tokenPermissions :token-permissions
|
||||
:communityTokensMetadata :tokens-metadata})
|
||||
:communityTokensMetadata :tokens-metadata
|
||||
:muteTill :muted-till})
|
||||
(update :admin-settings
|
||||
set/rename-keys
|
||||
{:pinMessageAllMembersEnabled :pin-message-all-members-enabled?})
|
||||
@@ -938,3 +941,58 @@
|
||||
{:error %
|
||||
:event
|
||||
:communities/check-and-delete-pending-request-to-join-community})}]})
|
||||
|
||||
(rf/defn mute-community-chats
|
||||
{:events [:community/mute-community-chats]}
|
||||
[{:keys [db]} chat-id muted? muted-till]
|
||||
(log/debug "muted community chat successfully" chat-id muted?)
|
||||
{:db (update-in db [:chats chat-id] merge {:muted muted? :muted-till muted-till})})
|
||||
|
||||
(rf/defn mute-and-unmute-community-chats
|
||||
{:events [:community/update-community-chats-mute-status]}
|
||||
[{:keys [db]} community-id muted? mute-till]
|
||||
(let [channels (get-in db [:communities community-id :chats])
|
||||
chats (mapv vector (keys channels) (vals channels))]
|
||||
(doseq [x chats]
|
||||
(doseq [{:keys [id]} x]
|
||||
(let [chat-id (str community-id id)]
|
||||
(rf/dispatch [:community/mute-community-chats chat-id muted? mute-till]))))))
|
||||
|
||||
(rf/defn mute-chat-failed
|
||||
{:events [:community/mute-community-failed]}
|
||||
[{:keys [db]} community-id muted? error]
|
||||
(log/error "mute community failed" community-id error)
|
||||
{:db (update-in db [:communities community-id :muted] (not muted?))}
|
||||
(rf/dispatch [:community/update-community-chats-mute-status community-id muted? error]))
|
||||
|
||||
(rf/defn mute-community-successfully
|
||||
{:events [:community/mute-community-successful]}
|
||||
[{:keys [db]} community-id muted? muted-till]
|
||||
(log/debug "muted community successfully" community-id muted-till)
|
||||
(rf/dispatch [:community/update-community-chats-mute-status community-id muted? muted-till])
|
||||
(let [time-string (fn [mute-title mute-duration]
|
||||
(i18n/label mute-title {:duration mute-duration}))]
|
||||
{:db (assoc-in db [:communities community-id :muted-till] muted-till)
|
||||
:dispatch [:toasts/upsert
|
||||
{:icon :correct
|
||||
:icon-color (quo2.colors/theme-colors
|
||||
quo2.colors/success-60
|
||||
quo2.colors/success-50)
|
||||
:text (if muted?
|
||||
(when (some? muted-till)
|
||||
(time-string :t/muted-until (format-mute-till muted-till)))
|
||||
(i18n/label :t/community-unmuted))}]}))
|
||||
|
||||
|
||||
(rf/defn set-community-muted
|
||||
{:events [:community/set-muted]}
|
||||
[{:keys [db]} community-id muted? muted-type]
|
||||
(let [params (if muted? [{:communityId community-id :mutedType muted-type}] [community-id])
|
||||
method (if muted? "wakuext_muteCommunityChats" "wakuext_unMuteCommunityChats")]
|
||||
{:db (assoc-in db [:communities community-id :muted] muted?)
|
||||
:json-rpc/call [{:method method
|
||||
:params params
|
||||
:on-error #(rf/dispatch [:community/mute-community-failed community-id
|
||||
muted? %])
|
||||
:on-success #(rf/dispatch [:community/mute-community-successful
|
||||
community-id muted? %])}]}))
|
||||
|
||||
@@ -39,10 +39,11 @@
|
||||
(contact.db/public-key->new-contact identity)))
|
||||
|
||||
(defn contact-two-names-by-identity
|
||||
[contact current-multiaccount identity]
|
||||
(let [me? (= (:public-key current-multiaccount) identity)]
|
||||
[contact profile identity]
|
||||
(let [me? (= (:public-key profile) identity)]
|
||||
(if me?
|
||||
[(or (:preferred-name current-multiaccount)
|
||||
[(or (:preferred-name profile)
|
||||
(:display-name profile)
|
||||
(:primary-name contact)
|
||||
(gfycat/generate-gfy identity))]
|
||||
[(:primary-name contact) (:secondary-name contact)])))
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
||||
(defn handle
|
||||
[override-theme]
|
||||
[theme]
|
||||
{:width 32
|
||||
:height 4
|
||||
:background-color (colors/theme-colors colors/neutral-100 colors/white override-theme)
|
||||
:opacity (theme/theme-value 0.05 0.1)
|
||||
:background-color (colors/theme-colors colors/neutral-100 colors/white theme)
|
||||
:opacity (theme/theme-value 0.05 0.1 theme)
|
||||
:border-radius 100
|
||||
:align-self :center
|
||||
:margin-vertical 8})
|
||||
|
||||
(defn sheet
|
||||
[{:keys [top bottom]} window-height override-theme padding-bottom-override shell?]
|
||||
[{:keys [top bottom]} window-height theme padding-bottom-override shell?]
|
||||
{:position :absolute
|
||||
:max-height (- window-height top)
|
||||
:z-index 1
|
||||
@@ -28,7 +28,7 @@
|
||||
:padding-bottom (or padding-bottom-override (+ bottom 8))
|
||||
:background-color (if shell?
|
||||
:transparent
|
||||
(colors/theme-colors colors/white colors/neutral-90 override-theme))})
|
||||
(colors/theme-colors colors/white colors/neutral-95 theme))})
|
||||
|
||||
(def shell-bg
|
||||
{:position :absolute
|
||||
@@ -39,7 +39,7 @@
|
||||
:bottom 0})
|
||||
|
||||
(defn selected-item
|
||||
[override-theme window-height sheet-height {:keys [top]}]
|
||||
[theme window-height sheet-height {:keys [top]}]
|
||||
{:position :absolute
|
||||
:bottom 10
|
||||
:max-height (- window-height sheet-height top)
|
||||
@@ -48,4 +48,4 @@
|
||||
:right 0
|
||||
:border-radius 12
|
||||
:margin-horizontal 8
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-90 override-theme)})
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns status-im2.common.bottom-sheet.view
|
||||
(:require [oops.core :as oops]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.blur :as blur]
|
||||
[react-native.core :as rn]
|
||||
[react-native.gesture :as gesture]
|
||||
@@ -52,11 +53,11 @@
|
||||
(show translate-y bg-opacity)
|
||||
(hide translate-y bg-opacity window-height on-close))))))
|
||||
|
||||
(defn f-view
|
||||
(defn- f-view
|
||||
[_ _]
|
||||
(let [sheet-height (reagent/atom 0)]
|
||||
(fn [{:keys [hide? insets]}
|
||||
{:keys [content override-theme selected-item padding-bottom-override on-close shell?]}]
|
||||
(fn [{:keys [hide? insets theme]}
|
||||
{:keys [content selected-item padding-bottom-override on-close shell?]}]
|
||||
(let [{window-height :height} (rn/get-window)
|
||||
bg-opacity (reanimated/use-shared-value 0)
|
||||
translate-y (reanimated/use-shared-value window-height)
|
||||
@@ -85,7 +86,7 @@
|
||||
{:transform [{:translateY translate-y}]}
|
||||
(style/sheet insets
|
||||
window-height
|
||||
override-theme
|
||||
theme
|
||||
padding-bottom-override
|
||||
shell?))
|
||||
:on-layout #(reset! sheet-height (oops/oget % "nativeEvent" "layout" "height"))}
|
||||
@@ -93,10 +94,16 @@
|
||||
[blur/ios-view {:style style/shell-bg}])
|
||||
(when selected-item
|
||||
[rn/view
|
||||
[rn/view {:style (style/selected-item override-theme window-height @sheet-height insets)}
|
||||
[rn/view {:style (style/selected-item theme window-height @sheet-height insets)}
|
||||
[selected-item]]])
|
||||
|
||||
;; handle
|
||||
[rn/view {:style (style/handle override-theme)}]
|
||||
[rn/view {:style (style/handle theme)}]
|
||||
;; content
|
||||
[content]]]]))))
|
||||
|
||||
(defn- internal-view
|
||||
[args sheet]
|
||||
[:f> f-view args sheet])
|
||||
|
||||
(def view (theme/with-theme internal-view))
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
[status-im2.contexts.contacts.drawers.nickname-drawer.view :as nickname-drawer]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.common.mute-chat-drawer.view :as mute-chat-drawer]
|
||||
[utils.datetime :as datetime]))
|
||||
[status-im2.common.mute-drawer.view :as mute-drawer]
|
||||
[status-im2.common.muting.helpers :refer [format-mute-till]]))
|
||||
|
||||
(defn- entry
|
||||
[{:keys [icon label on-press danger? sub-label chevron? add-divider? accessibility-label]}]
|
||||
@@ -53,11 +53,15 @@
|
||||
:accessibility-label :edit-nickname}])}]))
|
||||
|
||||
(defn mute-chat-action
|
||||
[chat-id chat-type]
|
||||
[chat-id chat-type muted?]
|
||||
(hide-sheet-and-dispatch [:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[mute-chat-drawer/mute-chat-drawer chat-id
|
||||
:mute-chat-for-duration chat-type])}]))
|
||||
[mute-drawer/mute-drawer
|
||||
{:id chat-id
|
||||
:community? false
|
||||
:muted? (not muted?)
|
||||
:chat-type chat-type
|
||||
:accessibility-label :mute-community-title}])}]))
|
||||
|
||||
(defn unmute-chat-action
|
||||
[chat-id]
|
||||
@@ -133,10 +137,10 @@
|
||||
:sub-label (when (and muted? (some? muted-till))
|
||||
(str (i18n/label :t/muted-until)
|
||||
" "
|
||||
(datetime/format-mute-till muted-till)))
|
||||
(format-mute-till muted-till)))
|
||||
:on-press (if muted?
|
||||
#(unmute-chat-action chat-id)
|
||||
#(mute-chat-action chat-id chat-type))
|
||||
#(mute-chat-action chat-id chat-type muted?))
|
||||
:danger? false
|
||||
:accessibility-label :mute-chat
|
||||
:chevron? (not muted?)})))
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
(ns status-im2.common.mute-chat-drawer.view
|
||||
(:require [utils.i18n :as i18n]
|
||||
[quo2.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.common.mute-chat-drawer.style :as style]))
|
||||
|
||||
(defn not-community-chat?
|
||||
[chat-type]
|
||||
(contains? #{constants/public-chat-type
|
||||
constants/private-group-chat-type
|
||||
constants/one-to-one-chat-type}
|
||||
chat-type))
|
||||
|
||||
(defn hide-sheet-and-dispatch
|
||||
[event]
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(rf/dispatch event))
|
||||
|
||||
(defn mute-chat-drawer
|
||||
[chat-id accessibility-label chat-type]
|
||||
[rn/view {:accessibility-label accessibility-label}
|
||||
[quo/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:style (style/header-text)}
|
||||
(i18n/label
|
||||
(if (not-community-chat? chat-type)
|
||||
:t/mute-chat-capitialized
|
||||
:t/mute-channel))]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-for-15-mins)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch [:chat.ui/mute chat-id true
|
||||
constants/mute-for-15-mins-type]))}]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-for-1-hour)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch [:chat.ui/mute chat-id true
|
||||
constants/mute-for-1-hour-type]))}]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-for-8-hours)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch [:chat.ui/mute chat-id true
|
||||
constants/mute-for-8-hours-type]))}]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-for-1-week)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch [:chat.ui/mute chat-id true
|
||||
constants/mute-for-1-week]))}]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-till-unmute)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch [:chat.ui/mute chat-id true
|
||||
constants/mute-till-unmuted]))}]])
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
(ns status-im2.common.mute-chat-drawer.style
|
||||
(ns status-im2.common.mute-drawer.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(defn header-text
|
||||
@@ -0,0 +1,100 @@
|
||||
(ns status-im2.common.mute-drawer.view
|
||||
(:require [utils.i18n :as i18n]
|
||||
[quo2.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.common.mute-drawer.style :as style]))
|
||||
|
||||
(defn not-community-chat?
|
||||
[chat-type]
|
||||
(contains? #{constants/public-chat-type
|
||||
constants/private-group-chat-type
|
||||
constants/one-to-one-chat-type}
|
||||
chat-type))
|
||||
|
||||
(defn hide-sheet-and-dispatch
|
||||
[{:keys [id community? muted? muted-type]}]
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(if community?
|
||||
(rf/dispatch [:community/set-muted id muted? muted-type])
|
||||
(rf/dispatch [:chat.ui/mute id muted? muted-type])))
|
||||
|
||||
(defn mute-drawer
|
||||
[{:keys [id accessibility-label muted? chat-type community?]}]
|
||||
[rn/view {:accessibility-label accessibility-label}
|
||||
[quo/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:style (style/header-text)}
|
||||
(i18n/label
|
||||
(if community?
|
||||
:t/mute-community
|
||||
(if (not-community-chat? chat-type)
|
||||
:t/mute-chat-capitialized
|
||||
:t/mute-channel)))]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-for-15-mins)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch
|
||||
{:id id
|
||||
:chat-type chat-type
|
||||
:muted? muted?
|
||||
:community? community?
|
||||
:muted-type constants/mute-for-15-mins-type}))}]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-for-1-hour)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch
|
||||
{:id id
|
||||
:chat-type chat-type
|
||||
:muted? muted?
|
||||
:community? community?
|
||||
:muted-type constants/mute-for-1-hour-type}))}]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-for-8-hours)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch
|
||||
{:id id
|
||||
:chat-type chat-type
|
||||
:muted? muted?
|
||||
:community? community?
|
||||
:muted-type constants/mute-for-8-hours-type}))}]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-for-1-week)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch
|
||||
{:id id
|
||||
:chat-type chat-type
|
||||
:muted? muted?
|
||||
:community? community?
|
||||
:muted-type constants/mute-for-1-week}))}]
|
||||
[quo/menu-item
|
||||
{:type :transparent
|
||||
:title (i18n/label :t/mute-till-unmute)
|
||||
:icon-bg-color :transparent
|
||||
:container-padding-vertical 12
|
||||
:title-column-style {:margin-left 2}
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch
|
||||
{:id id
|
||||
:chat-type chat-type
|
||||
:muted? muted?
|
||||
:community? community?
|
||||
:muted-type constants/mute-till-unmuted}))}]])
|
||||
@@ -0,0 +1,39 @@
|
||||
(ns status-im2.common.muting.helpers
|
||||
(:require [cljs-time.core :as t]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.datetime :refer
|
||||
[go-default-time int->weekday months
|
||||
time-zone-offset today? tomorrow?]])
|
||||
(:require [cljs-time.format :as t.format]))
|
||||
|
||||
(defn- add-leading-zero
|
||||
[input-string]
|
||||
(if (> 10 input-string)
|
||||
(str "0" input-string)
|
||||
input-string))
|
||||
|
||||
(defn format-mute-till
|
||||
[muted-till-string]
|
||||
(let [parsed-time (t.format/parse (t.format/formatters :date-time-no-ms) muted-till-string)
|
||||
hours-and-minutes (str (add-leading-zero (t/hour (t/plus parsed-time time-zone-offset)))
|
||||
":"
|
||||
(add-leading-zero (t/minute parsed-time)))
|
||||
when-to-unmute (cond (= go-default-time
|
||||
muted-till-string) (i18n/label :t/until-you-turn-it-back-on)
|
||||
(today? parsed-time) (str hours-and-minutes " today")
|
||||
(tomorrow? parsed-time) (str hours-and-minutes " tomorrow")
|
||||
:else (str hours-and-minutes
|
||||
" "
|
||||
(i18n/label
|
||||
(keyword "t"
|
||||
(get int->weekday
|
||||
(t/day-of-week
|
||||
parsed-time))))
|
||||
" "
|
||||
(t/day parsed-time)
|
||||
" "
|
||||
(i18n/label
|
||||
(keyword "t"
|
||||
(get months
|
||||
(t/month parsed-time))))))]
|
||||
when-to-unmute))
|
||||
@@ -0,0 +1,83 @@
|
||||
(ns status-im2.common.muting.helpers-test
|
||||
(:require [cljs-time.coerce :as time-coerce]
|
||||
[cljs-time.core :as t]
|
||||
[cljs-time.format :as t.format]
|
||||
[cljs.test :refer-macros [deftest testing is are]]
|
||||
[clojure.string :as string]
|
||||
[utils.datetime :as datetime]
|
||||
[status-im2.common.muting.helpers :as muting]))
|
||||
|
||||
(def mock-current-time-epoch 1655731506000)
|
||||
|
||||
(deftest format-mute-till-test
|
||||
(with-redefs [t/*ms-fn* (constantly mock-current-time-epoch)]
|
||||
(let [remove-msecs #(string/replace % #"\.\w*Z" "Z")
|
||||
time-str-to-obj #(t.format/parse (remove-msecs (time-coerce/to-string %)))
|
||||
curr-time (t/now)
|
||||
custom-HH-MM-formatter (t.format/formatter "HH:mm")
|
||||
custom-DD-formatter (t.format/formatter "DD")
|
||||
get-hh-mm #(t.format/unparse custom-HH-MM-formatter %)
|
||||
get-day #(t.format/unparse custom-DD-formatter %)
|
||||
get-week-day #(->> %
|
||||
t/day-of-week
|
||||
(get datetime/int->weekday))
|
||||
mock-today (t.format/unparse (t.format/formatters :date-time-no-ms) curr-time)
|
||||
in-n-days #(-> (time-str-to-obj mock-today)
|
||||
(t/plus (t/days %)))
|
||||
in-n-minutes #(-> (time-str-to-obj mock-today)
|
||||
(t/plus (t/minutes %)))
|
||||
in-n-hours #(-> (time-str-to-obj mock-today)
|
||||
(t/plus (t/hours %)))
|
||||
mock-tomorrow (in-n-days 1)
|
||||
mock-in-two-days (in-n-days 2)
|
||||
mock-in-three-days (in-n-days 3)
|
||||
mock-in-four-days (in-n-days 4)
|
||||
mock-in-five-days (in-n-days 5)
|
||||
mock-in-six-days (in-n-days 6)
|
||||
mock-in-15-minutes (in-n-minutes 15)
|
||||
mock-in-1-hour (in-n-hours 1)
|
||||
mock-in-8-hour (in-n-hours 8)
|
||||
get-month-day-int #(int (get-day %))
|
||||
today? (fn [mocked curr-time]
|
||||
(=
|
||||
(t.format/unparse (t.format/formatter "MM:DD") mocked)
|
||||
(t.format/unparse (t.format/formatter "MM:DD") curr-time)))
|
||||
tomorrow? (fn [mocked curr-time]
|
||||
(some #(= %
|
||||
(-
|
||||
(int (get-month-day-int mocked))
|
||||
(int (get-month-day-int curr-time))))
|
||||
[1 30 29 27]))
|
||||
form-full-date #(str (get-hh-mm %)
|
||||
" " (string/capitalize (get-week-day %))
|
||||
" " (get-month-day-int %)
|
||||
" " (string/capitalize (get datetime/months (t/month %))))
|
||||
today-date #(str (get-hh-mm %) " today")
|
||||
tomorrow-date #(str (get-hh-mm %) " tomorrow")
|
||||
write-date #(cond (today? % curr-time) (today-date %)
|
||||
(tomorrow? % curr-time) (tomorrow-date %)
|
||||
:else (form-full-date %))
|
||||
will-unmute-in-1-hour (remove-msecs (time-coerce/to-string mock-in-1-hour))
|
||||
will-unmute-in-8-hours (remove-msecs (time-coerce/to-string mock-in-8-hour))
|
||||
will-unmute-in-15-mins (remove-msecs (time-coerce/to-string mock-in-15-minutes))
|
||||
will-unmute-in-two-days (remove-msecs (time-coerce/to-string mock-in-two-days))
|
||||
will-unmute-tomorrow (remove-msecs (time-coerce/to-string mock-tomorrow))
|
||||
will-unmute-in-three-days (remove-msecs (time-coerce/to-string mock-in-three-days))
|
||||
will-unmute-in-four-days (remove-msecs (time-coerce/to-string mock-in-four-days))
|
||||
will-unmute-in-five-days (remove-msecs (time-coerce/to-string mock-in-five-days))
|
||||
will-unmute-in-six-days (remove-msecs (time-coerce/to-string mock-in-six-days))]
|
||||
(testing "Mute for minutes and hours"
|
||||
(are [arg expected] (= (muting/format-mute-till arg) expected)
|
||||
will-unmute-in-15-mins (write-date mock-in-15-minutes)
|
||||
will-unmute-in-1-hour (write-date mock-in-1-hour)
|
||||
will-unmute-in-8-hours (write-date mock-in-8-hour)))
|
||||
(testing "Weekdays"
|
||||
(are [arg expected] (= (muting/format-mute-till arg) expected)
|
||||
will-unmute-tomorrow (write-date mock-tomorrow)
|
||||
will-unmute-in-two-days (write-date mock-in-two-days)
|
||||
will-unmute-in-three-days (write-date mock-in-three-days)
|
||||
will-unmute-in-four-days (write-date mock-in-four-days)
|
||||
will-unmute-in-five-days (write-date mock-in-five-days)
|
||||
will-unmute-in-six-days (write-date mock-in-six-days)))
|
||||
(testing "Until the user turns it back on"
|
||||
(is (= "you turn it back on" (muting/format-mute-till datetime/go-default-time)))))))
|
||||
@@ -14,7 +14,9 @@
|
||||
(def ^:const content-type-gap 10)
|
||||
(def ^:const content-type-contact-request 11) ;; TODO: temp, will be removed
|
||||
(def ^:const content-type-system-pinned-message 14)
|
||||
(def ^:const content-type-system-mutual-state-update 15)
|
||||
(def ^:const content-type-system-message-mutual-event-sent 15)
|
||||
(def ^:const content-type-system-message-mutual-event-accepted 16)
|
||||
(def ^:const content-type-system-message-mutual-event-removed 17)
|
||||
|
||||
;; Not implemented in status-go, only used for testing/ui work
|
||||
(def ^:const content-type-gif 100)
|
||||
@@ -46,6 +48,7 @@
|
||||
(def ^:const mute-for-8-hours-type 3)
|
||||
(def ^:const mute-for-1-week 4)
|
||||
(def ^:const mute-till-unmuted 5)
|
||||
(def ^:const un-muted 0)
|
||||
|
||||
(def ^:const activity-center-mark-all-as-read-undo-time-limit-ms 4000)
|
||||
(def ^:const activity-center-max-unread-count 99)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
[{:keys [sending-images? sending-links?]}
|
||||
{:keys [text-value focused? maximized?]}
|
||||
{:keys [height saved-height last-height opacity background-y container-opacity]}
|
||||
window-height]
|
||||
window-height edit]
|
||||
(reanimated/animate height comp-constants/input-height)
|
||||
(reanimated/set-shared-value saved-height comp-constants/input-height)
|
||||
(reanimated/set-shared-value last-height comp-constants/input-height)
|
||||
@@ -36,12 +36,13 @@
|
||||
(reset! text-value "")
|
||||
(reset! sending-links? false)
|
||||
(reset! sending-images? false)
|
||||
(messages.list/scroll-to-bottom))
|
||||
(when-not (some? edit)
|
||||
(messages.list/scroll-to-bottom)))
|
||||
|
||||
(defn f-send-button
|
||||
[props {:keys [text-value] :as state}
|
||||
animations window-height images?
|
||||
btn-opacity z-index]
|
||||
btn-opacity z-index edit]
|
||||
(rn/use-effect (fn []
|
||||
(if (or (seq @text-value) images?)
|
||||
(when (or (not= @z-index 1) (not= (reanimated/get-shared-value btn-opacity) 1))
|
||||
@@ -58,14 +59,14 @@
|
||||
{:icon true
|
||||
:size 32
|
||||
:accessibility-label :send-message-button
|
||||
:on-press #(send-message props state animations window-height)}
|
||||
:on-press #(send-message props state animations window-height edit)}
|
||||
:i/arrow-up]])
|
||||
|
||||
(defn send-button
|
||||
[props {:keys [text-value] :as state} animations window-height images?]
|
||||
[props {:keys [text-value] :as state} animations window-height images? edit]
|
||||
(let [btn-opacity (reanimated/use-shared-value 0)
|
||||
z-index (reagent/atom (if (and (empty? @text-value) (not images?)) 0 1))]
|
||||
[:f> f-send-button props state animations window-height images? btn-opacity z-index]))
|
||||
[:f> f-send-button props state animations window-height images? btn-opacity z-index edit]))
|
||||
|
||||
(defn disabled-audio-button
|
||||
[]
|
||||
@@ -215,7 +216,7 @@
|
||||
[image-button props animations insets]
|
||||
[reaction-button]
|
||||
[format-button]]
|
||||
[:f> send-button props state animations window-height images]
|
||||
[:f> send-button props state animations window-height images edit]
|
||||
(when (and (not edit) (not images))
|
||||
;; TODO(alwx): needs to be replaced with an `audio-button` later.
|
||||
;; See https://github.com/status-im/status-mobile/issues/16084 for more details.
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
[status-im.utils.types :as types]
|
||||
[reagent.core :as reagent]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[utils.datetime :as datetime]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.async-storage.core :as async-storage]
|
||||
[status-im2.contexts.shell.jump-to.constants :as shell.constants]))
|
||||
[status-im2.contexts.shell.jump-to.constants :as shell.constants]
|
||||
[status-im2.common.muting.helpers :refer [format-mute-till]]))
|
||||
|
||||
(defn- get-chat
|
||||
[cofx chat-id]
|
||||
@@ -293,6 +293,12 @@
|
||||
(deactivate-chat chat-id)
|
||||
(offload-messages chat-id)))
|
||||
|
||||
(rf/defn unmute-chat-community
|
||||
{:events [:chat/unmute-chat-community]}
|
||||
[{:keys [db]} chat-id muted?]
|
||||
(let [{:keys [community-id]} (get-in db [:chats chat-id])]
|
||||
{:db (assoc-in db [:communities community-id :muted] muted?)}))
|
||||
|
||||
(rf/defn mute-chat-failed
|
||||
{:events [:chat/mute-failed]}
|
||||
[{:keys [db]} chat-id muted? error]
|
||||
@@ -303,6 +309,8 @@
|
||||
{:events [:chat/mute-successfully]}
|
||||
[{:keys [db]} chat-id muted-till mute-type muted? chat-type]
|
||||
(log/debug "muted chat successfully" chat-id " for" muted-till)
|
||||
(when-not muted?
|
||||
(rf/dispatch [:chat/unmute-chat-community chat-id muted?]))
|
||||
(let [time-string (fn [duration-kw unmute-time]
|
||||
(i18n/label duration-kw {:duration unmute-time}))
|
||||
not-community-chat? #(contains? #{constants/public-chat-type
|
||||
@@ -348,7 +356,7 @@
|
||||
:icon-color (colors/theme-colors colors/success-60
|
||||
colors/success-50)
|
||||
:text (mute-duration-text (when (some? muted-till)
|
||||
(str (datetime/format-mute-till muted-till))))}]}))
|
||||
(str (format-mute-till muted-till))))}]}))
|
||||
|
||||
(rf/defn mute-chat
|
||||
{:events [:chat.ui/mute]}
|
||||
@@ -361,7 +369,7 @@
|
||||
:params params
|
||||
:on-error #(rf/dispatch [:chat/mute-failed chat-id muted? %])
|
||||
:on-success #(rf/dispatch [:chat/mute-successfully chat-id % mute-type
|
||||
(not muted?) chat-type])}]}))
|
||||
muted? chat-type])}]}))
|
||||
|
||||
(rf/defn show-clear-history-confirmation
|
||||
{:events [:chat.ui/show-clear-history-confirmation]}
|
||||
|
||||
@@ -111,12 +111,15 @@
|
||||
:style {:color colors/neutral-40}} (when timestamp (datetime/to-short-str timestamp))]]]
|
||||
[rn/view {:style style/top-right-buttons}
|
||||
[rn/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:on-press #(share-image messages @index)
|
||||
:style (merge style/close-container {:margin-right 12})}
|
||||
{:active-opacity 1
|
||||
:accessibility-label :share-image
|
||||
:on-press #(share-image messages @index)
|
||||
:style (merge style/close-container {:margin-right 12})}
|
||||
[quo/icon :share {:size 20 :color colors/white}]]
|
||||
[rn/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:on-press #(rf/dispatch [:show-bottom-sheet {:content (fn [] [drawer messages @index])}])
|
||||
:style style/close-container}
|
||||
{:active-opacity 1
|
||||
:accessibility-label :image-options
|
||||
:on-press #(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [drawer messages @index])}])
|
||||
:style style/close-container}
|
||||
[quo/icon :options {:size 20 :color colors/white}]]]]))
|
||||
|
||||
@@ -15,19 +15,22 @@
|
||||
(defn mention-tag-wrapper
|
||||
[]
|
||||
{:flex-direction :row
|
||||
:align-items :center})
|
||||
:align-items :center
|
||||
:height 22})
|
||||
|
||||
(def mention-tag
|
||||
{:background-color colors/primary-50-opa-10
|
||||
:padding-horizontal 3
|
||||
:border-radius 6
|
||||
:margin-bottom -3})
|
||||
:margin-bottom -3
|
||||
:height 22})
|
||||
|
||||
(def mention-tag-text
|
||||
{:color (colors/theme-colors colors/primary-50
|
||||
colors/primary-60)
|
||||
:selection-color :transparent
|
||||
:suppress-highlighting true})
|
||||
:suppress-highlighting true
|
||||
:line-height 21})
|
||||
|
||||
(defn code
|
||||
[]
|
||||
|
||||
@@ -63,7 +63,8 @@
|
||||
:style style/mention-tag}
|
||||
[quo/text
|
||||
{:weight :medium
|
||||
:style style/mention-tag-text}
|
||||
:style style/mention-tag-text
|
||||
:size :paragraph-1}
|
||||
(rf/sub [:messages/resolve-mention literal])]]])
|
||||
|
||||
:edited
|
||||
|
||||
@@ -253,7 +253,10 @@
|
||||
[{:keys [type value content-type] :as message-data} _ _
|
||||
{:keys [context keyboard-shown?]}]
|
||||
;;TODO temporary hide mutual-state-updates https://github.com/status-im/status-mobile/issues/16254
|
||||
(when (not= content-type constants/content-type-system-mutual-state-update)
|
||||
(when-not (#{constants/content-type-system-message-mutual-event-sent
|
||||
constants/content-type-system-message-mutual-event-accepted
|
||||
constants/content-type-system-message-mutual-event-removed}
|
||||
content-type)
|
||||
[rn/view
|
||||
(add-inverted-y-android {:background-color (colors/theme-colors colors/white colors/neutral-95)})
|
||||
(cond
|
||||
@@ -373,10 +376,11 @@
|
||||
(- (reanimated/get-shared-value scroll-y)
|
||||
keyboard-height))))
|
||||
[keyboard-shown keyboard-height])
|
||||
;; Note - Don't pass `behavior :height` to keyboard avoiding view,
|
||||
;; It breaks composer - https://github.com/status-im/status-mobile/issues/16595
|
||||
[rn/keyboard-avoiding-view
|
||||
{:style (style/keyboard-avoiding-container insets)
|
||||
:keyboard-vertical-offset (- (:bottom insets))
|
||||
:behavior :height}
|
||||
:keyboard-vertical-offset (- (:bottom insets))}
|
||||
|
||||
(when header-comp
|
||||
[header-comp
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
(ns status-im2.contexts.communities.actions.chat.view
|
||||
(:require [quo2.core :as quo]
|
||||
[status-im2.common.not-implemented :as not-implemented]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.i18n :as i18n]
|
||||
[status-im2.common.mute-chat-drawer.view :as mute-chat-drawer]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.common.muting.helpers :refer [format-mute-till]]
|
||||
[status-im2.common.mute-drawer.view :as mute-drawer]))
|
||||
|
||||
(defn hide-sheet-and-dispatch
|
||||
[event]
|
||||
@@ -12,11 +12,15 @@
|
||||
(rf/dispatch event))
|
||||
|
||||
(defn- mute-channel-action
|
||||
[chat-id chat-type]
|
||||
[chat-id chat-type muted?]
|
||||
(hide-sheet-and-dispatch [:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[mute-chat-drawer/mute-chat-drawer chat-id
|
||||
:mute-chat-for-duration chat-type])}]))
|
||||
[mute-drawer/mute-drawer
|
||||
{:id chat-id
|
||||
:community? false
|
||||
:muted? (not muted?)
|
||||
:chat-type chat-type
|
||||
:accessibility-label :mute-community-title}])}]))
|
||||
|
||||
(defn- unmute-channel-action
|
||||
[chat-id]
|
||||
@@ -46,16 +50,16 @@
|
||||
|
||||
(defn- action-toggle-muted
|
||||
[id muted? muted-till chat-type]
|
||||
(let [muted (and muted? (some? muted-till))]
|
||||
(let [muted (and muted? (some? muted-till))
|
||||
time-string (fn [mute-title mute-duration]
|
||||
(i18n/label mute-title {:duration mute-duration}))]
|
||||
(cond-> {:icon :i/muted
|
||||
:accessibility-label :chat-toggle-muted
|
||||
:sub-label (when muted
|
||||
(str (i18n/label :t/muted-until)
|
||||
" "
|
||||
(datetime/format-mute-till muted-till)))
|
||||
:sub-label (when (and muted? (some? muted-till))
|
||||
(time-string :t/muted-until (format-mute-till muted-till)))
|
||||
:on-press (if muted?
|
||||
#(unmute-channel-action id)
|
||||
#(mute-channel-action id chat-type))
|
||||
#(mute-channel-action id chat-type muted?))
|
||||
:label (i18n/label (if muted
|
||||
:t/unmute-channel
|
||||
:t/mute-channel))}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
[quo2.core :as quo]
|
||||
[status-im2.contexts.communities.actions.see-rules.view :as see-rules]
|
||||
[status-im2.contexts.communities.actions.leave.view :as leave-menu]
|
||||
[status-im2.common.mute-drawer.view :as mute-options]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.common.muting.helpers :refer [format-mute-till]]
|
||||
[status-im2.contexts.communities.actions.token-gating.view :as token-gating]))
|
||||
|
||||
(defn hide-sheet-and-dispatch
|
||||
@@ -45,12 +48,26 @@
|
||||
:on-press #(hide-sheet-and-dispatch [:chat.ui/mark-all-read-in-community-pressed id])})
|
||||
|
||||
(defn mute-community
|
||||
[_ muted?]
|
||||
{:icon (if muted? :i/muted :i/activity-center)
|
||||
:accessibility-label (if muted? :unmute-community :mute-community)
|
||||
:label (i18n/label (if muted? :t/unmute-community :t/mute-community))
|
||||
:sub-label (when muted? (str "muted for 15 minutes"))
|
||||
:right-icon :i/chevron-right})
|
||||
[id muted? muted-till]
|
||||
(let [time-string (fn [mute-title mute-duration]
|
||||
(i18n/label mute-title {:duration mute-duration}))]
|
||||
{:icon (if muted? :i/muted :i/activity-center)
|
||||
:accessibility-label (if muted? :unmute-community :mute-community)
|
||||
:label (i18n/label (if muted? :t/unmute-community :t/mute-community))
|
||||
:sub-label (when (and muted? (some? muted-till))
|
||||
(time-string :t/muted-until (format-mute-till muted-till)))
|
||||
:right-icon :i/chevron-right
|
||||
:on-press (if muted?
|
||||
#(hide-sheet-and-dispatch [:community/set-muted id (not muted?)
|
||||
constants/un-muted])
|
||||
#(hide-sheet-and-dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[mute-options/mute-drawer
|
||||
{:id id
|
||||
:community? true
|
||||
:muted? (not muted?)
|
||||
:accessibility-label :mute-community-title}])}]))}))
|
||||
|
||||
(defn community-notification-settings
|
||||
[id]
|
||||
@@ -119,12 +136,12 @@
|
||||
(not-joined-options id token-gated?))
|
||||
|
||||
(defn joined-options
|
||||
[id token-gated? muted?]
|
||||
[id token-gated? muted? muted-till]
|
||||
[[(view-members id)
|
||||
(view-rules id)
|
||||
(when token-gated? (view-token-gating id))
|
||||
(mark-as-read id)
|
||||
(mute-community id muted?)
|
||||
(mute-community id muted? muted-till)
|
||||
(community-notification-settings id)
|
||||
(invite-contacts id)
|
||||
(show-qr id)
|
||||
@@ -132,12 +149,12 @@
|
||||
[(assoc (leave-community id) :add-divider? true)]])
|
||||
|
||||
(defn owner-options
|
||||
[id token-gated? muted?]
|
||||
[id token-gated? muted? muted-till]
|
||||
[[(view-members id)
|
||||
(view-rules id)
|
||||
(when token-gated? (view-token-gating id))
|
||||
(mark-as-read id)
|
||||
(mute-community id muted?)
|
||||
(mute-community id muted? muted-till)
|
||||
(community-notification-settings id)
|
||||
(invite-contacts id)
|
||||
(show-qr id)
|
||||
@@ -146,11 +163,11 @@
|
||||
(defn get-context-drawers
|
||||
[{:keys [id]}]
|
||||
(let [{:keys [token-permissions admin joined
|
||||
muted banList]} (rf/sub [:communities/community id])
|
||||
request-id (rf/sub [:communities/my-pending-request-to-join id])]
|
||||
muted banList muted-till]} (rf/sub [:communities/community id])
|
||||
request-id (rf/sub [:communities/my-pending-request-to-join id])]
|
||||
(cond
|
||||
admin (owner-options id token-permissions muted)
|
||||
joined (joined-options id token-permissions muted)
|
||||
admin (owner-options id token-permissions muted muted-till)
|
||||
joined (joined-options id token-permissions muted muted-till)
|
||||
request-id (join-request-sent-options id token-permissions request-id)
|
||||
banList (banned-options id token-permissions)
|
||||
:else (not-joined-options id token-permissions))))
|
||||
|
||||
@@ -28,12 +28,12 @@
|
||||
cover {:uri (get-in (:images item) [:banner :uri])}]
|
||||
(if (= view-type :card-view)
|
||||
[quo/community-card-view-item (assoc item :width width :cover cover)
|
||||
#(rf/dispatch [:navigate-to :community-overview (:id item)])]
|
||||
#(rf/dispatch [:communities/navigate-to-community (:id item)])]
|
||||
[quo/community-list-item
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:communities/load-category-states (:id item)])
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(rf/dispatch [:navigate-to :community-overview (:id item)]))
|
||||
(rf/dispatch [:communities/navigate-to-community (:id item)]))
|
||||
:on-long-press #(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn []
|
||||
@@ -141,12 +141,12 @@
|
||||
(merge community
|
||||
(get mock-community-item-data :data)
|
||||
{:cover cover})
|
||||
#(rf/dispatch [:navigate-to :community-overview (:id community)])]
|
||||
#(rf/dispatch [:communities/navigate-to-community (:id community)])]
|
||||
[quo/community-list-item
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:communities/load-category-states (:id community)])
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(rf/dispatch [:navigate-to :community-overview (:id community)]))
|
||||
(rf/dispatch [:communities/navigate-to-community (:id community)]))
|
||||
:on-long-press #(js/alert "TODO: to be implemented")}
|
||||
(merge community
|
||||
(get mock-community-item-data :data))])]))
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
{:content (fn []
|
||||
[options/community-options-bottom-sheet id])
|
||||
:selected-item (fn []
|
||||
[quo/communities-membership-list-item {} item])}])}
|
||||
[quo/communities-membership-list-item {} true item])}])}
|
||||
false
|
||||
item]))
|
||||
|
||||
(def tabs-data
|
||||
|
||||
@@ -122,7 +122,8 @@
|
||||
[:show-bottom-sheet
|
||||
{:content
|
||||
(fn []
|
||||
[method-menu/view on-change-profile-pic])}]))
|
||||
[method-menu/view on-change-profile-pic])
|
||||
:theme :dark}]))
|
||||
:image-picker-props {:profile-picture (when @profile-pic {:uri @profile-pic})
|
||||
:full-name (if (seq @full-name)
|
||||
@full-name
|
||||
|
||||
@@ -62,10 +62,8 @@
|
||||
[[{:icon :i/camera
|
||||
:accessibility-label :take-photo-button
|
||||
:label (i18n/label :t/profile-pic-take)
|
||||
:override-theme :dark
|
||||
:on-press #(take-pic update-profile-pic-callback)}
|
||||
{:icon :i/image
|
||||
:override-theme :dark
|
||||
:accessibility-label :select-from-gallery-button
|
||||
:label (i18n/label :t/profile-pic-pick)
|
||||
:on-press #(pick-pic update-profile-pic-callback)}]]])
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
{:padding-vertical 60
|
||||
:justify-content :center}
|
||||
[community-list-view/communities-membership-list-item {}
|
||||
false
|
||||
(cond-> (merge @state data/community)
|
||||
(= :muted (:notifications @state))
|
||||
(assoc :muted? true)
|
||||
|
||||
@@ -16,58 +16,47 @@
|
||||
{:label "Show red options?"
|
||||
:key :show-red-options?
|
||||
:type :boolean}
|
||||
{:label "Override theme"
|
||||
:key :override-theme
|
||||
{:label "Drawer theme"
|
||||
:key :theme
|
||||
:type :select
|
||||
:options [{:key :dark :value "Dark"}
|
||||
{:key :light :value "Light"}
|
||||
{:key nil :value "System"}]}])
|
||||
|
||||
(defn options-with-consequences
|
||||
[override-theme]
|
||||
[{:icon :i/delete
|
||||
:danger? true
|
||||
:label "Clear history"
|
||||
:override-theme override-theme
|
||||
:add-divider? true
|
||||
:on-press #(js/alert "clear history")}])
|
||||
(def options-with-consequences
|
||||
[{:icon :i/delete
|
||||
:danger? true
|
||||
:label "Clear history"
|
||||
:add-divider? true
|
||||
:on-press #(js/alert "clear history")}])
|
||||
|
||||
(defn render-action-sheet
|
||||
[state]
|
||||
(let [override-theme (:override-theme @state)]
|
||||
[rn/view
|
||||
{:height 300
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95)}
|
||||
[quo/action-drawer
|
||||
(cond->
|
||||
[[{:icon :i/friend
|
||||
:label "View channel members and details"
|
||||
:override-theme override-theme
|
||||
:on-press #(js/alert "View channel members and details")}
|
||||
{:icon :i/communities
|
||||
:label "Mark as read"
|
||||
:override-theme override-theme
|
||||
:disabled? (:mark-as-read-disabled? @state)
|
||||
:on-press #(js/alert "Mark as read")}
|
||||
{:icon :i/muted
|
||||
:label (if (:muted? @state) "Unmute channel" "Mute channel")
|
||||
:override-theme override-theme
|
||||
:on-press #(js/alert (if (:muted? @state) "Unmute channel" "Mute channel"))
|
||||
:right-icon :i/chevron-right
|
||||
:sub-label (when (:muted? @state) "Muted for 15 min")}
|
||||
{:icon :i/scan
|
||||
:on-press #(js/alert "Fetch messages")
|
||||
:override-theme override-theme
|
||||
:right-icon :i/chevron-right
|
||||
:right-text "3"
|
||||
:label "Fetch messages"}
|
||||
{:icon :i/add-user
|
||||
:override-theme override-theme
|
||||
:on-press #(js/alert "Share link to the channel")
|
||||
:label "Share link to the channel"}]]
|
||||
[quo/action-drawer
|
||||
(cond->
|
||||
[[{:icon :i/friend
|
||||
:label "View channel members and details"
|
||||
:on-press #(js/alert "View channel members and details")}
|
||||
{:icon :i/communities
|
||||
:label "Mark as read"
|
||||
:disabled? (:mark-as-read-disabled? @state)
|
||||
:on-press #(js/alert "Mark as read")}
|
||||
{:icon :i/muted
|
||||
:label (if (:muted? @state) "Unmute channel" "Mute channel")
|
||||
:on-press #(js/alert (if (:muted? @state) "Unmute channel" "Mute channel"))
|
||||
:right-icon :i/chevron-right
|
||||
:sub-label (when (:muted? @state) "Muted for 15 min")}
|
||||
{:icon :i/scan
|
||||
:on-press #(js/alert "Fetch messages")
|
||||
:right-icon :i/chevron-right
|
||||
:right-text "3"
|
||||
:label "Fetch messages"}
|
||||
{:icon :i/add-user
|
||||
:on-press #(js/alert "Share link to the channel")
|
||||
:label "Share link to the channel"}]]
|
||||
|
||||
(:show-red-options? @state)
|
||||
(conj (options-with-consequences override-theme)))]]))
|
||||
(:show-red-options? @state)
|
||||
(conj options-with-consequences))])
|
||||
|
||||
(defn cool-preview
|
||||
[]
|
||||
@@ -80,7 +69,8 @@
|
||||
[quo/button
|
||||
{:style {:margin-horizontal 40}
|
||||
:on-press #(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [render-action-sheet state])}])}
|
||||
{:content (fn [] [render-action-sheet state])
|
||||
:theme (:theme @state)}])}
|
||||
"See in bottom sheet"]
|
||||
[rn/view {:padding-vertical 60}
|
||||
[render-action-sheet state]]]])))
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
[status-im2.contexts.quo-preview.wallet.network-amount :as network-amount]
|
||||
[status-im2.contexts.quo-preview.wallet.network-breakdown :as network-breakdown]
|
||||
[status-im2.contexts.quo-preview.wallet.token-overview :as token-overview]
|
||||
[status-im2.contexts.quo-preview.settings.category :as category]
|
||||
[status-im2.contexts.quo-preview.keycard.keycard :as keycard]
|
||||
[status-im2.contexts.quo-preview.loaders.skeleton :as skeleton]
|
||||
[status-im2.contexts.quo-preview.community.channel-actions :as channel-actions]))
|
||||
@@ -330,7 +331,10 @@
|
||||
:component settings-list/preview-settings-list}
|
||||
{:name :reorder-item
|
||||
:options {:topBar {:visible true}}
|
||||
:component reorder-item/preview-reorder-item}]
|
||||
:component reorder-item/preview-reorder-item}
|
||||
{:name :category
|
||||
:options {:topBar {:visible true}}
|
||||
:component category/preview}]
|
||||
:share [{:name :qr-code
|
||||
:options {:topBar {:visible true}}
|
||||
:component qr-code/preview-qr-code}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
(ns status-im2.contexts.quo-preview.settings.category
|
||||
(:require
|
||||
[quo2.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
(def item
|
||||
{:title "Item 1"
|
||||
:left-icon :i/browser
|
||||
:chevron? true})
|
||||
|
||||
(def descriptor
|
||||
[{:label "Category label:"
|
||||
:key :label
|
||||
:type :text}
|
||||
{:label "Category size:"
|
||||
:key :size
|
||||
:type :text}
|
||||
{:label "Blur:"
|
||||
:key :blur?
|
||||
:type :boolean}])
|
||||
|
||||
(def image-uri
|
||||
"https://4kwallpapers.com/images/wallpapers/giau-pass-mountain-pass-italy-dolomites-landscape-mountain-750x1334-4282.jpg")
|
||||
|
||||
(def label "Label")
|
||||
|
||||
(defn preview
|
||||
[]
|
||||
(let [state (reagent/atom {:label "Label"
|
||||
:size "5"
|
||||
:blur? false})
|
||||
{:keys [width height]} (rn/get-window)]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [data (repeat (js/parseInt (:size @state)) item)]
|
||||
(rn/use-effect (fn []
|
||||
(if (:blur? @state)
|
||||
(theme/set-theme :dark)
|
||||
(theme/set-theme :light)))
|
||||
[(:blur? @state)])
|
||||
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:padding-bottom 150
|
||||
:margin-bottom 50
|
||||
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)}}
|
||||
[rn/view
|
||||
{:style {:min-height 180
|
||||
:z-index 1}} [preview/customizer state descriptor]]
|
||||
(when (:blur? @state)
|
||||
[fast-image/fast-image
|
||||
{:source {:uri image-uri}
|
||||
:style {:width width
|
||||
:height height
|
||||
:position :absolute}}])
|
||||
[rn/view
|
||||
{:style {:background-color (if (:blur? @state)
|
||||
colors/neutral-80-opa-80
|
||||
(colors/theme-colors colors/neutral-5 colors/neutral-95))}}
|
||||
[quo/category {:label (:label @state) :data data :blur? (:blur? @state)}]]]]))]))
|
||||
+2
-2
@@ -83,8 +83,8 @@
|
||||
(defn- prepare-challenge-reply
|
||||
[props]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content view
|
||||
:override-theme :dark}
|
||||
{:content view
|
||||
:theme :dark}
|
||||
(assoc props :replying? true)]))
|
||||
|
||||
(defn- send-challenge-reply
|
||||
|
||||
@@ -39,19 +39,18 @@
|
||||
[]
|
||||
(let [unread-count (rf/sub [:activity-center/unread-count])]
|
||||
[quo/action-drawer
|
||||
[[{:icon :i/mark-as-read
|
||||
:override-theme :dark
|
||||
:label (i18n/label :t/mark-all-notifications-as-read)
|
||||
:disabled? (zero? unread-count)
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:activity-center.notifications/mark-all-as-read-locally
|
||||
(fn []
|
||||
{:icon :up-to-date
|
||||
:icon-color colors/success-50
|
||||
:text (i18n/label :t/notifications-marked-as-read
|
||||
{:count unread-count})
|
||||
:override-theme :dark})])
|
||||
(rf/dispatch [:hide-bottom-sheet]))}]]]))
|
||||
[[{:icon :i/mark-as-read
|
||||
:label (i18n/label :t/mark-all-notifications-as-read)
|
||||
:disabled? (zero? unread-count)
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:activity-center.notifications/mark-all-as-read-locally
|
||||
(fn []
|
||||
{:icon :up-to-date
|
||||
:icon-color colors/success-50
|
||||
:text (i18n/label :t/notifications-marked-as-read
|
||||
{:count unread-count})
|
||||
:override-theme :dark})])
|
||||
(rf/dispatch [:hide-bottom-sheet]))}]]]))
|
||||
|
||||
(defn empty-tab
|
||||
[]
|
||||
@@ -144,7 +143,6 @@
|
||||
:type :blur-bg
|
||||
:size 32
|
||||
:accessibility-label :close-activity-center
|
||||
:override-theme :dark
|
||||
:on-press #(rf/dispatch [:navigate-back])}
|
||||
:i/close]
|
||||
[quo/button
|
||||
@@ -152,10 +150,9 @@
|
||||
:type :blur-bg
|
||||
:size 32
|
||||
:accessibility-label :activity-center-open-more
|
||||
:override-theme :dark
|
||||
:on-press #(rf/dispatch [:show-bottom-sheet
|
||||
{:content options-bottom-sheet-content
|
||||
:override-theme :dark}])}
|
||||
{:content options-bottom-sheet-content
|
||||
:theme :dark}])}
|
||||
:i/options]]
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
[react-native.reanimated :as reanimated]))
|
||||
|
||||
(def screen-padding 20)
|
||||
(def flash-button-size 32)
|
||||
(def flash-button-spacing 12)
|
||||
|
||||
(def flex-spacer {:flex 1})
|
||||
|
||||
@@ -69,10 +71,11 @@
|
||||
{:flex-direction :row
|
||||
:justify-content :space-between})
|
||||
|
||||
(def camera-flash-button
|
||||
(defn camera-flash-button
|
||||
[viewfinder]
|
||||
{:position :absolute
|
||||
:right 20
|
||||
:bottom 20})
|
||||
:top (- (+ (:y viewfinder) (:height viewfinder)) flash-button-size flash-button-spacing)
|
||||
:right (+ screen-padding flash-button-spacing)})
|
||||
|
||||
(defn border
|
||||
[border1 border2 corner]
|
||||
|
||||
@@ -3,21 +3,22 @@
|
||||
[oops.core :as oops]
|
||||
[quo2.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.blur :as blur]
|
||||
[react-native.camera-kit :as camera-kit]
|
||||
[react-native.core :as rn]
|
||||
[react-native.blur :as blur]
|
||||
[react-native.hole-view :as hole-view]
|
||||
[react-native.permissions :as permissions]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.syncing.scan-sync-code.style :as style]
|
||||
[status-im2.contexts.syncing.utils :as sync-utils]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.contexts.syncing.utils :as sync-utils]
|
||||
[status-im.utils.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.debounce :as debounce]))
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
;; Android allow local network access by default. So, we need this check on iOS only.
|
||||
(defonce preflight-check-passed? (reagent/atom (if platform/ios? false true)))
|
||||
@@ -175,9 +176,7 @@
|
||||
[rn/view
|
||||
{:style style/qr-view-finder
|
||||
:on-layout (fn [event]
|
||||
(let [layout (js->clj (oops/oget event "nativeEvent.layout")
|
||||
:keywordize-keys
|
||||
true)
|
||||
(let [layout (transforms/js->clj (oops/oget event "nativeEvent.layout"))
|
||||
view-finder (assoc layout :height (:width layout))]
|
||||
(reset! qr-view-finder view-finder)))}])
|
||||
|
||||
@@ -215,15 +214,7 @@
|
||||
[rn/view
|
||||
[border :border-bottom-width :border-right-width :border-bottom-right-radius]
|
||||
[border-tip {:right 0 :top -1}]
|
||||
[border-tip {:left -1 :bottom 0}]]]
|
||||
[quo/button
|
||||
{:icon true
|
||||
:type :blur-bg
|
||||
:size 32
|
||||
:accessibility-label :camera-flash
|
||||
:override-background-color colors/neutral-80-opa-40
|
||||
:style style/camera-flash-button}
|
||||
:i/flashlight-off]]
|
||||
[border-tip {:left -1 :bottom 0}]]]]
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :regular
|
||||
@@ -279,7 +270,7 @@
|
||||
:text (i18n/label :t/error-this-is-not-a-sync-qr-code)}]))))
|
||||
|
||||
(defn render-camera
|
||||
[show-camera? qr-view-finder camera-ref on-read-code show-holes?]
|
||||
[show-camera? torch-mode qr-view-finder camera-ref on-read-code]
|
||||
(when (and show-camera? (:x qr-view-finder))
|
||||
[:<>
|
||||
[rn/view {:style style/camera-container}
|
||||
@@ -288,16 +279,13 @@
|
||||
:style style/camera-style
|
||||
:camera-type camera-kit/camera-type-back
|
||||
:zoom-mode :off
|
||||
;; https://github.com/status-im/status-mobile/issues/16243
|
||||
:torch-mode :off
|
||||
:torch-mode torch-mode
|
||||
:scan-barcode true
|
||||
:on-read-code on-read-code}]]
|
||||
[hole-view/hole-view
|
||||
{:style style/hole
|
||||
:holes (if show-holes?
|
||||
[(merge qr-view-finder
|
||||
{:borderRadius 16})]
|
||||
[])}
|
||||
:holes [(merge qr-view-finder
|
||||
{:borderRadius 16})]}
|
||||
[blur/view
|
||||
{:style style/absolute-fill
|
||||
:blur-amount 10
|
||||
@@ -307,13 +295,17 @@
|
||||
|
||||
(defn f-view
|
||||
[{:keys [title show-bottom-view? background animated?]}]
|
||||
(let [insets (safe-area/get-insets)
|
||||
active-tab (reagent/atom 1)
|
||||
qr-view-finder (reagent/atom {})
|
||||
render-camera? (reagent/atom false)]
|
||||
(let [insets (safe-area/get-insets)
|
||||
active-tab (reagent/atom 1)
|
||||
qr-view-finder (reagent/atom {})
|
||||
render-camera? (reagent/atom false)
|
||||
torch? (reagent/atom false)
|
||||
app-state-listener (atom nil)]
|
||||
(fn []
|
||||
(let [camera-ref (atom nil)
|
||||
read-qr-once? (atom false)
|
||||
torch-mode (if @torch? :on :off)
|
||||
flashlight-icon (if @torch? :i/flashlight-on :i/flashlight-off)
|
||||
;; The below check is to prevent scanning of any QR code
|
||||
;; when the user is in syncing progress screen
|
||||
user-in-syncing-progress-screen? (= (rf/sub [:view-id]) :syncing-progress)
|
||||
@@ -328,9 +320,8 @@
|
||||
scan-qr-code-tab? (= @active-tab 1)
|
||||
show-camera? (and scan-qr-code-tab?
|
||||
@camera-permission-granted?
|
||||
@preflight-check-passed?)
|
||||
show-holes? (and show-camera?
|
||||
(boolean (not-empty @qr-view-finder)))
|
||||
@preflight-check-passed?
|
||||
(boolean (not-empty @qr-view-finder)))
|
||||
title-opacity (reanimated/use-shared-value (if animated? 0 1))
|
||||
subtitle-opacity (reanimated/use-shared-value (if animated? 0 1))
|
||||
content-opacity (reanimated/use-shared-value (if animated? 0 1))
|
||||
@@ -361,6 +352,13 @@
|
||||
0
|
||||
:easing4))
|
||||
(if show-camera? 500 0)))]
|
||||
(rn/use-effect (fn []
|
||||
(reset! app-state-listener
|
||||
(.addEventListener rn/app-state
|
||||
"change"
|
||||
#(when (and (not= % "active") @torch?)
|
||||
(reset! torch? false))))
|
||||
#(.remove @app-state-listener)))
|
||||
(when animated?
|
||||
(reanimated/animate-shared-value-with-delay subtitle-opacity
|
||||
1 constants/onboarding-modal-animation-duration
|
||||
@@ -399,7 +397,7 @@
|
||||
[:<>
|
||||
background
|
||||
(when (or (not animated?) @render-camera?)
|
||||
[render-camera show-camera? @qr-view-finder camera-ref on-read-code show-holes?])
|
||||
[render-camera show-camera? torch-mode @qr-view-finder camera-ref on-read-code])
|
||||
[rn/view {:style (style/root-container (:top insets))}
|
||||
[header
|
||||
{:active-tab active-tab
|
||||
@@ -419,11 +417,21 @@
|
||||
:transform [{:translate-y content-translate-y}]}
|
||||
{})}
|
||||
(case @active-tab
|
||||
1 [scan-qr-code-tab qr-view-finder request-camera-permission]
|
||||
1 [scan-qr-code-tab qr-view-finder]
|
||||
2 [enter-sync-code-tab]
|
||||
nil)]
|
||||
[rn/view {:style style/flex-spacer}]
|
||||
(when show-bottom-view? [bottom-view insets bottom-view-translate-y])]]))))
|
||||
(when show-bottom-view? [bottom-view insets bottom-view-translate-y])
|
||||
(when (and (or (not animated?) @render-camera?) show-camera?)
|
||||
[quo/button
|
||||
{:icon true
|
||||
:type :blur-bg
|
||||
:size style/flash-button-size
|
||||
:accessibility-label :camera-flash
|
||||
:override-background-color colors/neutral-80-opa-40
|
||||
:style (style/camera-flash-button @qr-view-finder)
|
||||
:on-press #(swap! torch? not)}
|
||||
flashlight-icon])]]))))
|
||||
|
||||
(defn view
|
||||
[props]
|
||||
|
||||
@@ -131,7 +131,6 @@
|
||||
{:label (i18n/label :t/have-a-sync-code?)
|
||||
:increase-padding-top? true}]
|
||||
[quo/action-drawer
|
||||
[[{:icon :i/scan
|
||||
:override-theme :dark
|
||||
:on-press #(rf/dispatch [:navigate-to :scan-sync-code-page])
|
||||
:label (i18n/label :t/scan-or-enter-sync-code)}]]]]]])))
|
||||
[[{:icon :i/scan
|
||||
:on-press #(rf/dispatch [:navigate-to :scan-sync-code-page])
|
||||
:label (i18n/label :t/scan-or-enter-sync-code)}]]]]]])))
|
||||
|
||||
@@ -75,10 +75,11 @@
|
||||
(def bottom-sheet
|
||||
(reagent/reactify-component
|
||||
(fn []
|
||||
(let [{:keys [sheets hide? theme]} (rf/sub [:bottom-sheet])
|
||||
sheet (last sheets)
|
||||
insets (safe-area/get-insets)
|
||||
user-theme (theme/get-theme)]
|
||||
(let [{:keys [sheets hide?]} (rf/sub [:bottom-sheet])
|
||||
sheet (last sheets)
|
||||
{:keys [theme]} sheet
|
||||
insets (safe-area/get-insets)
|
||||
user-theme (theme/get-theme)]
|
||||
^{:key (str "sheet" @reloader/cnt)}
|
||||
[theme/provider {:theme (or theme user-theme)}
|
||||
[inactive]
|
||||
@@ -86,7 +87,7 @@
|
||||
{:style {:position :relative :flex 1}
|
||||
:keyboard-vertical-offset (- (max 20 (:bottom insets)))}
|
||||
(when sheet
|
||||
[:f> bottom-sheet/f-view {:insets insets :hide? hide?}
|
||||
[bottom-sheet/view {:insets insets :hide? hide?}
|
||||
sheet])]]))))
|
||||
|
||||
(def toasts (reagent/reactify-component toasts/toasts))
|
||||
|
||||
@@ -296,35 +296,3 @@
|
||||
(def ^:const go-default-time
|
||||
"Zero value for golang's time var"
|
||||
"0001-01-01T00:00:00Z")
|
||||
|
||||
(defn- add-leading-zero
|
||||
[input-string]
|
||||
(if (> 10 input-string)
|
||||
(str "0" input-string)
|
||||
input-string))
|
||||
|
||||
(defn format-mute-till
|
||||
[muted-till-string]
|
||||
(let [parsed-time (t.format/parse (t.format/formatters :date-time-no-ms) muted-till-string)
|
||||
hours-and-minutes (str (add-leading-zero (t/hour (t/plus parsed-time time-zone-offset)))
|
||||
":"
|
||||
(add-leading-zero (t/minute parsed-time)))
|
||||
when-to-unmute (cond (= go-default-time
|
||||
muted-till-string) (i18n/label :t/until-you-turn-it-back-on)
|
||||
(today? parsed-time) (str hours-and-minutes " today")
|
||||
(tomorrow? parsed-time) (str hours-and-minutes " tomorrow")
|
||||
:else (str hours-and-minutes
|
||||
" "
|
||||
(i18n/label
|
||||
(keyword "t"
|
||||
(get int->weekday
|
||||
(t/day-of-week
|
||||
parsed-time))))
|
||||
" "
|
||||
(t/day parsed-time)
|
||||
" "
|
||||
(i18n/label
|
||||
(keyword "t"
|
||||
(get months
|
||||
(t/month parsed-time))))))]
|
||||
when-to-unmute))
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
(def epoch 0)
|
||||
;; 1970-01-03 00:00:00 UTC
|
||||
(def epoch-plus-3d 172800000)
|
||||
(def mock-current-time-epoch 1655731506000)
|
||||
|
||||
(deftest is-24-hour-locale-en-test
|
||||
(is (= (#'utils.datetime/is-24-hour-locsym (i18n-goog/locale-symbols "en")) false)))
|
||||
@@ -189,77 +190,3 @@
|
||||
"it"
|
||||
#'utils.datetime/medium-date-time-format)]
|
||||
(is (= (datetime/day-relative epoch) "01 gen 1970, 12:00:00 AM")))))
|
||||
|
||||
;; TODO(@ibrkhalil): This test sometimes fail in the CI. Make the test agnostic
|
||||
;; of the current time and timezone.
|
||||
#_(deftest format-mute-till-test
|
||||
(let [remove-msecs #(string/replace % #"\.\w*Z" "Z")
|
||||
time-str-to-obj #(t.format/parse (remove-msecs (time-coerce/to-string %)))
|
||||
curr-time (t/now)
|
||||
custom-HH-MM-formatter (t.format/formatter "HH:mm")
|
||||
custom-DD-formatter (t.format/formatter "DD")
|
||||
get-hh-mm #(t.format/unparse custom-HH-MM-formatter %)
|
||||
get-day #(t.format/unparse custom-DD-formatter %)
|
||||
get-week-day #(->> %
|
||||
t/day-of-week
|
||||
(get datetime/int->weekday))
|
||||
mock-today (t.format/unparse (t.format/formatters :date-time-no-ms) curr-time)
|
||||
in-n-days #(-> (time-str-to-obj mock-today)
|
||||
(t/plus (t/days %)))
|
||||
in-n-minutes #(-> (time-str-to-obj mock-today)
|
||||
(t/plus (t/minutes %)))
|
||||
in-n-hours #(-> (time-str-to-obj mock-today)
|
||||
(t/plus (t/hours %)))
|
||||
mock-tomorrow (in-n-days 1)
|
||||
mock-in-two-days (in-n-days 2)
|
||||
mock-in-three-days (in-n-days 3)
|
||||
mock-in-four-days (in-n-days 4)
|
||||
mock-in-five-days (in-n-days 5)
|
||||
mock-in-six-days (in-n-days 6)
|
||||
mock-in-15-minutes (in-n-minutes 15)
|
||||
mock-in-1-hour (in-n-hours 1)
|
||||
mock-in-8-hour (in-n-hours 8)
|
||||
get-month-day-int #(int (get-day %))
|
||||
today? (fn [mocked curr-time]
|
||||
(=
|
||||
(t.format/unparse (t.format/formatter "MM:DD") mocked)
|
||||
(t.format/unparse (t.format/formatter "MM:DD") curr-time)))
|
||||
tomorrow? (fn [mocked curr-time]
|
||||
(some #(= %
|
||||
(-
|
||||
(int (get-month-day-int mocked))
|
||||
(int (get-month-day-int curr-time))))
|
||||
[1 30 29 27]))
|
||||
form-full-date #(str (get-hh-mm %)
|
||||
" " (string/capitalize (get-week-day %))
|
||||
" " (get-month-day-int %)
|
||||
" " (string/capitalize (get datetime/months (t/month %))))
|
||||
today-date #(str (get-hh-mm %) " today")
|
||||
tomorrow-date #(str (get-hh-mm %) " tomorrow")
|
||||
write-date #(cond (today? % curr-time) (today-date %)
|
||||
(tomorrow? % curr-time) (tomorrow-date %)
|
||||
:else (form-full-date %))
|
||||
will-unmute-in-1-hour (remove-msecs (time-coerce/to-string mock-in-1-hour))
|
||||
will-unmute-in-8-hours (remove-msecs (time-coerce/to-string mock-in-8-hour))
|
||||
will-unmute-in-15-mins (remove-msecs (time-coerce/to-string mock-in-15-minutes))
|
||||
will-unmute-in-two-days (remove-msecs (time-coerce/to-string mock-in-two-days))
|
||||
will-unmute-tomorrow (remove-msecs (time-coerce/to-string mock-tomorrow))
|
||||
will-unmute-in-three-days (remove-msecs (time-coerce/to-string mock-in-three-days))
|
||||
will-unmute-in-four-days (remove-msecs (time-coerce/to-string mock-in-four-days))
|
||||
will-unmute-in-five-days (remove-msecs (time-coerce/to-string mock-in-five-days))
|
||||
will-unmute-in-six-days (remove-msecs (time-coerce/to-string mock-in-six-days))]
|
||||
(testing "Mute for minutes and hours"
|
||||
(are [arg expected] (= (datetime/format-mute-till arg) expected)
|
||||
will-unmute-in-15-mins (write-date mock-in-15-minutes)
|
||||
will-unmute-in-1-hour (write-date mock-in-1-hour)
|
||||
will-unmute-in-8-hours (write-date mock-in-8-hour)))
|
||||
(testing "Weekdays"
|
||||
(are [arg expected] (= (datetime/format-mute-till arg) expected)
|
||||
will-unmute-tomorrow (write-date mock-tomorrow)
|
||||
will-unmute-in-two-days (write-date mock-in-two-days)
|
||||
will-unmute-in-three-days (write-date mock-in-three-days)
|
||||
will-unmute-in-four-days (write-date mock-in-four-days)
|
||||
will-unmute-in-five-days (write-date mock-in-five-days)
|
||||
will-unmute-in-six-days (write-date mock-in-six-days)))
|
||||
(testing "Until the user turns it back on"
|
||||
(is (= "you turn it back on" (datetime/format-mute-till datetime/go-default-time))))))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
|
||||
"owner": "status-im",
|
||||
"repo": "status-go",
|
||||
"version": "v0.161.6",
|
||||
"commit-sha1": "3170300d780e2f1479d467546d21f247f54463b5",
|
||||
"src-sha256": "10nx6gw0p5hnaxic5azb9wz392fxpzmbacvkwd8n1lammkcyqh1d"
|
||||
"version": "v0.162.5",
|
||||
"commit-sha1": "cf2d72bfa83f094719a93b7b7fd5a68e3a68ab47",
|
||||
"src-sha256": "0x79nm1n6gbgz2lzsky29laap7m0r0hggrb2fsn07bf0xw0364qn"
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ from appium.webdriver.common.mobileby import MobileBy
|
||||
from sauceclient import SauceException
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
from urllib3.exceptions import MaxRetryError, ProtocolError
|
||||
|
||||
@@ -312,7 +313,7 @@ def create_shared_drivers(quantity):
|
||||
test_suite_data.current_test.testruns[-1].error = "Not all %s drivers are created" % quantity
|
||||
return drivers, loop
|
||||
except (MaxRetryError, AttributeError) as e:
|
||||
test_suite_data.current_test.testruns[-1].error += "%s" % e.reason
|
||||
test_suite_data.current_test.testruns[-1].error += str(e)
|
||||
raise e
|
||||
|
||||
|
||||
@@ -418,7 +419,7 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
|
||||
test.testruns[-1].first_commands[session_id] = commands.index(command) + 1
|
||||
except KeyError:
|
||||
continue
|
||||
except (RemoteDisconnected, requests.exceptions.ConnectionError):
|
||||
except (RemoteDisconnected, requests.exceptions.ConnectionError, TimeoutException):
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@@ -1098,7 +1098,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
|
||||
messages = ['hello', '¿Cómo estás tu año?', 'ё, доброго вечерочка', '® æ ç ♥']
|
||||
[self.chat_2.send_message(message) for message in messages]
|
||||
for message in messages:
|
||||
if not self.chat_1.chat_element_by_text(message).is_element_displayed():
|
||||
if not self.chat_1.chat_element_by_text(message).is_element_displayed(10):
|
||||
self.errors.append("Message with text '%s' was not received" % message)
|
||||
|
||||
self.chat_2.just_fyi("Checking updated member photo, timestamp and username on message")
|
||||
@@ -1206,6 +1206,67 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
|
||||
self.errors.append("Message reaction is not shown for the sender")
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(703391)
|
||||
def test_1_1_chat_send_image_save_and_share(self):
|
||||
if not self.chat_2.chat_message_input.is_element_displayed():
|
||||
self.chat_2.jump_to_card_by_text(self.username_1)
|
||||
if not self.chat_1.chat_message_input.is_element_displayed():
|
||||
self.chat_1.jump_to_card_by_text(self.username_2)
|
||||
|
||||
self.chat_1.just_fyi("Device 1 sends an image")
|
||||
image_description = "test image"
|
||||
self.chat_1.send_images_with_description(description=image_description, indexes=[2])
|
||||
|
||||
self.chat_2.just_fyi("Device 2 checks image message")
|
||||
if not self.chat_2.chat_element_by_text(image_description).is_element_displayed(30):
|
||||
self.chat_2.hide_keyboard_if_shown()
|
||||
self.chat_2.chat_element_by_text(image_description).wait_for_visibility_of_element(30)
|
||||
if not self.chat_2.chat_element_by_text(
|
||||
image_description).image_in_message.is_element_image_similar_to_template('saucelabs_sauce_chat.png'):
|
||||
self.errors.append("Not expected image is shown to the receiver.")
|
||||
|
||||
for chat in self.chat_1, self.chat_2:
|
||||
chat.just_fyi("Open the image and share it")
|
||||
if not chat.chat_element_by_text(image_description).image_in_message.is_element_displayed():
|
||||
chat.hide_keyboard_if_shown()
|
||||
chat.chat_element_by_text(image_description).image_in_message.click()
|
||||
chat.share_image_icon_button.click()
|
||||
chat.element_starts_with_text("Gmail").click()
|
||||
try:
|
||||
chat.wait_for_current_package_to_be('com.google.android.gm')
|
||||
except TimeoutException:
|
||||
self.errors.append(
|
||||
"%s can't share an image via Gmail." % ("Sender" if chat is self.chat_1 else "Receiver"))
|
||||
chat.click_system_back_button_until_element_is_shown(element="chat")
|
||||
|
||||
for chat in self.chat_1, self.chat_2:
|
||||
chat.just_fyi("Open the image and save it")
|
||||
device_name = "sender" if chat is self.chat_1 else "receiver"
|
||||
chat.chat_element_by_text(image_description).image_in_message.click()
|
||||
chat.view_image_options_button.click()
|
||||
chat.save_image_icon_button.click()
|
||||
toast_element = chat.toast_content_element
|
||||
if toast_element.is_element_displayed():
|
||||
toast_element_text = toast_element.text
|
||||
if toast_element_text != chat.get_translation_by_key("photo-saved"):
|
||||
self.errors.append(
|
||||
"Shown message '%s' doesn't match expected '%s' after saving an image for %s." % (
|
||||
toast_element_text, chat.get_translation_by_key("photo-saved"), device_name))
|
||||
else:
|
||||
self.errors.append("Message about saving a photo is not shown for %s." % device_name)
|
||||
chat.click_system_back_button_until_element_is_shown(element="chat")
|
||||
|
||||
for chat in self.chat_1, self.chat_2:
|
||||
chat.just_fyi("Check that image is saved in gallery")
|
||||
chat.show_images_button.click()
|
||||
chat.allow_button.click_if_shown()
|
||||
if not chat.get_image_by_index(0).is_element_image_similar_to_template("saucelabs_sauce_gallery.png"):
|
||||
self.errors.append(
|
||||
"Image is not saved to gallery for %s." % ("sender" if chat is self.chat_1 else "receiver"))
|
||||
chat.click_system_back_button()
|
||||
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(702733)
|
||||
def test_1_1_chat_text_message_delete_push_disappear(self):
|
||||
if not self.chat_2.chat_message_input.is_element_displayed():
|
||||
@@ -1272,7 +1333,11 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
|
||||
self.chat_2.jump_to_card_by_text(self.username_1)
|
||||
self.chat_1.jump_to_card_by_text(self.username_2)
|
||||
self.home_1.just_fyi('Turn on airplane mode and check that offline status is shown on home view')
|
||||
[home.toggle_airplane_mode() for home in self.homes]
|
||||
for home in self.homes:
|
||||
home.toggle_airplane_mode()
|
||||
if not home.chats_tab.is_element_displayed() and not home.chat_floating_screen.is_element_displayed():
|
||||
home.driver.launch_app()
|
||||
SignInView(home.driver).sign_in()
|
||||
|
||||
# Not implemented yet
|
||||
# self.home_1.connection_offline_icon.wait_and_click(20)
|
||||
@@ -1285,6 +1350,9 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
|
||||
message_1 = 'test message'
|
||||
|
||||
self.home_2.just_fyi('Device2 checks "Sending" status when sending message from offline')
|
||||
if not self.chat_2.chat_message_input.is_element_displayed():
|
||||
self.home_2.chats_tab.click()
|
||||
self.home_2.get_chat(self.username_1).click()
|
||||
self.chat_2.send_message(message_1)
|
||||
status = self.chat_2.chat_element_by_text(message_1).status
|
||||
if not (status == 'Sending' or status == 'Sent'):
|
||||
@@ -1293,8 +1361,9 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
|
||||
self.home_2.just_fyi('Device2 goes back online and checks that status of the message is changed to "delivered"')
|
||||
for i, home in enumerate(self.homes):
|
||||
home.toggle_airplane_mode()
|
||||
if "im.status.ethereum" not in home.driver.current_activity:
|
||||
home.click_system_back_button_until_element_is_shown()
|
||||
if not home.chats_tab.is_element_displayed() and not home.chat_floating_screen.is_element_displayed():
|
||||
home.driver.launch_app()
|
||||
SignInView(home.driver).sign_in()
|
||||
home.chats_tab.click()
|
||||
home.get_chat(self.username_2 if i == 0 else self.username_1).click()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
from _pytest.outcomes import Failed
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException
|
||||
|
||||
from tests import marks, run_in_parallel
|
||||
from tests.base_test_case import MultipleSharedDeviceTestCase, create_shared_drivers
|
||||
@@ -278,7 +278,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
|
||||
if username_shown != self.usernames[2]:
|
||||
self.errors.append(
|
||||
"Incorrect profile is opened from the list of reactions, username is %s but expected to be %s" % (
|
||||
username_shown, self.usernames[2])
|
||||
username_shown, self.usernames[2])
|
||||
)
|
||||
except NoSuchElementException:
|
||||
self.errors.append("User profile was not opened from the list of reactions")
|
||||
@@ -341,6 +341,64 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
|
||||
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(703297)
|
||||
def test_group_chat_send_image_save_and_share(self):
|
||||
[self.homes[i].click_system_back_button_until_element_is_shown() for i in range(3)]
|
||||
for i in range(3):
|
||||
self.homes[i].get_chat(self.chat_name).click()
|
||||
|
||||
self.chats[1].just_fyi("Member_1 sends an image")
|
||||
image_description = "test image"
|
||||
self.chats[1].send_images_with_description(description=image_description, indexes=[2])
|
||||
|
||||
self.chats[0].just_fyi("Admin checks image message")
|
||||
self.chats[0].chat_element_by_text(image_description).wait_for_visibility_of_element(60)
|
||||
if not self.chats[0].chat_element_by_text(
|
||||
image_description).image_in_message.is_element_image_similar_to_template('saucelabs_sauce_chat.png'):
|
||||
self.errors.append("Not expected image is shown to the admin.")
|
||||
|
||||
self.chats[2].just_fyi("Member_2 checks image message")
|
||||
self.chats[2].chat_element_by_text(image_description).wait_for_visibility_of_element(60)
|
||||
if not self.chats[2].chat_element_by_text(
|
||||
image_description).image_in_message.is_element_image_similar_to_template('saucelabs_sauce_chat.png'):
|
||||
self.errors.append("Not expected image is shown to the member_2.")
|
||||
|
||||
self.chats[0].just_fyi("Admin opens the image and shares it")
|
||||
self.chats[0].chat_element_by_text(image_description).image_in_message.click()
|
||||
self.chats[0].share_image_icon_button.click()
|
||||
self.chats[0].element_starts_with_text("Gmail").click()
|
||||
try:
|
||||
self.chats[0].wait_for_current_package_to_be('com.google.android.gm')
|
||||
except TimeoutException:
|
||||
self.errors.append("Admin can't share an image via Gmail.")
|
||||
self.chats[0].click_system_back_button_until_element_is_shown(element="chat")
|
||||
|
||||
self.chats[1].click_system_back_button_until_element_is_shown()
|
||||
|
||||
self.chats[2].just_fyi("Member_2 opens the image and saves it")
|
||||
self.chats[2].chat_element_by_text(image_description).image_in_message.click()
|
||||
self.chats[2].view_image_options_button.click()
|
||||
self.chats[2].save_image_icon_button.click()
|
||||
toast_element = self.chats[2].toast_content_element
|
||||
if toast_element.is_element_displayed():
|
||||
toast_element_text = toast_element.text
|
||||
if toast_element_text != self.chats[2].get_translation_by_key("photo-saved"):
|
||||
self.errors.append(
|
||||
"Shown message '%s' doesn't match expected '%s' after saving an image for member_2." % (
|
||||
toast_element_text, self.chats[2].get_translation_by_key("photo-saved")))
|
||||
else:
|
||||
self.errors.append("Message about saving a photo is not shown for member_2.")
|
||||
self.chats[2].click_system_back_button_until_element_is_shown(element="chat")
|
||||
|
||||
self.chats[2].just_fyi("Member_2 checks that image was saved in gallery")
|
||||
self.chats[2].show_images_button.click()
|
||||
self.chats[2].allow_button.click_if_shown()
|
||||
if not self.chats[2].get_image_by_index(0).is_element_image_similar_to_template("saucelabs_sauce_gallery.png"):
|
||||
self.errors.append("Image is not saved to gallery for member_2.")
|
||||
self.chats[2].click_system_back_button_until_element_is_shown(element="chat")
|
||||
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(702808)
|
||||
def test_group_chat_offline_pn(self):
|
||||
self.homes[0].click_system_back_button_until_element_is_shown()
|
||||
|
||||
@@ -540,73 +540,6 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
|
||||
self.errors.append("Deleted for me message is deleted all channel members")
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(702859)
|
||||
def test_community_one_image_send_reply(self):
|
||||
|
||||
self.home_1.just_fyi('Send image in 1-1 chat from Gallery')
|
||||
image_description = 'description'
|
||||
self.channel_1.send_images_with_description(image_description)
|
||||
# self.channel_1.chat_element_by_text(image_description).image_in_message.click()
|
||||
# self.channel_1.click_system_back_button_until_element_is_shown(element='chat')
|
||||
|
||||
# TODO: options for image are still WIP; add case with edit description of image and after 15901 fix
|
||||
self.home_2.just_fyi('check image, description and options for receiver')
|
||||
# self.channel_2.chat_element_by_text(image_description).image_in_message.click()
|
||||
# self.home_1.just_fyi('save image')
|
||||
# self.chat_1.save_image_button.click_until_presence_of_element(self.chat_1.show_images_button)
|
||||
# self.chat_1.show_images_button.click_until_presence_of_element(self.chat_1.image_from_gallery_button)
|
||||
# self.chat_1.image_from_gallery_button.click_until_presence_of_element(self.chat_1.recent_image_in_gallery)
|
||||
# if not self.chat_1.recent_image_in_gallery.is_element_displayed():
|
||||
# self.errors.append('Saved image is not shown in Recent')
|
||||
# self.home_1.click_system_back_button(2)
|
||||
# self.home_2.just_fyi('check share and save options on opened image')
|
||||
# self.chat_2.image_message_in_chat.scroll_to_element(direction='up')
|
||||
# self.chat_2.image_message_in_chat.click_until_presence_of_element(self.chat_2.share_image_icon_button)
|
||||
# self.chat_2.share_image_icon_button.click()
|
||||
# self.chat_2.share_via_messenger()
|
||||
# if not self.chat_2.image_in_android_messenger.is_element_displayed():
|
||||
# self.errors.append("Can't share image")
|
||||
# self.chat_2.click_system_back_button_until_element_is_shown(element=self.chat_2.save_image_icon_button)
|
||||
# self.chat_2.save_image_icon_button.click()
|
||||
# self.chat_2.show_images_button.click()
|
||||
# self.chat_2.allow_button.wait_and_click()
|
||||
#
|
||||
# if not self.chat_2.first_image_from_gallery.is_element_image_similar_to_template('saved.png'):
|
||||
# self.errors.append("New picture was not saved!")
|
||||
#
|
||||
# self.channel_2.chat_element_by_text(image_description).image_in_message.save_new_screenshot_of_element('images_test.png')
|
||||
|
||||
self.channel_2.chat_element_by_text(image_description).wait_for_visibility_of_element(60)
|
||||
if not self.channel_2.chat_element_by_text(
|
||||
image_description).image_in_message.is_element_image_similar_to_template('image_sent_in_community.png'):
|
||||
self.errors.append("Not expected image is shown to the receiver")
|
||||
|
||||
self.channel_2.just_fyi("Can reply to images")
|
||||
self.channel_2.quote_message(image_description)
|
||||
message_text = 'reply to image'
|
||||
self.channel_2.chat_message_input.send_keys(message_text)
|
||||
self.channel_2.send_message_button.click()
|
||||
chat_element_1 = self.channel_1.chat_element_by_text(message_text)
|
||||
if not chat_element_1.is_element_displayed(sec=60) or chat_element_1.replied_message_text != image_description:
|
||||
self.errors.append('Reply message was not received by the sender')
|
||||
|
||||
self.channel_2.just_fyi("Set a reaction for the image message")
|
||||
self.channel_2.set_reaction(message=image_description)
|
||||
try:
|
||||
self.channel_1.chat_element_by_text(image_description).emojis_below_message().wait_for_element_text(1)
|
||||
except (Failed, NoSuchElementException):
|
||||
self.errors.append("Image message reaction is not shown for the sender")
|
||||
|
||||
self.channel_1.just_fyi("Set a reaction for the message reply")
|
||||
self.channel_2.set_reaction(message=image_description, emoji="love")
|
||||
try:
|
||||
self.channel_2.chat_element_by_text(message_text).emojis_below_message(
|
||||
emoji="love").wait_for_element_text(1)
|
||||
except (Failed, NoSuchElementException):
|
||||
self.errors.append("Reply message reaction is not shown for the reply sender")
|
||||
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(703194)
|
||||
def test_community_several_images_send_reply(self):
|
||||
self.home_1.just_fyi('Send several images in 1-1 chat from Gallery')
|
||||
@@ -627,6 +560,19 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
|
||||
received = False
|
||||
|
||||
if received:
|
||||
self.channel_2.just_fyi("Checking an ability to save and share an image from gallery")
|
||||
chat_element.image_container_in_message.image_by_index(1).click()
|
||||
if not self.channel_2.share_image_icon_button.is_element_displayed():
|
||||
self.errors.append("Can't share an image from gallery.")
|
||||
if self.channel_2.view_image_options_button.is_element_displayed():
|
||||
self.channel_2.view_image_options_button.click()
|
||||
if not self.channel_2.save_image_icon_button.is_element_displayed():
|
||||
self.errors.append("Can't save an image from gallery.")
|
||||
else:
|
||||
self.errors.append("Image options button is not shown for an image from gallery.")
|
||||
|
||||
self.channel_2.click_system_back_button_until_element_is_shown(element="chat")
|
||||
|
||||
self.channel_2.just_fyi("Can reply to gallery")
|
||||
self.channel_2.quote_message(image_description)
|
||||
message_text = 'reply to gallery'
|
||||
@@ -638,6 +584,79 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
|
||||
self.errors.append('Reply message was not received by the sender')
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(702859)
|
||||
def test_community_one_image_send_reply(self):
|
||||
self.home_1.just_fyi('Send image in 1-1 chat from Gallery')
|
||||
image_description = 'description'
|
||||
self.channel_1.send_images_with_description(image_description)
|
||||
|
||||
self.home_2.just_fyi('Check image, description and options for receiver')
|
||||
if not self.channel_2.chat_element_by_text(image_description).is_element_displayed(60):
|
||||
self.channel_2.hide_keyboard_if_shown()
|
||||
self.channel_2.chat_element_by_text(image_description).wait_for_visibility_of_element(10)
|
||||
if not self.channel_2.chat_element_by_text(
|
||||
image_description).image_in_message.is_element_image_similar_to_template('image_sent_in_community.png'):
|
||||
self.errors.append("Not expected image is shown to the receiver")
|
||||
|
||||
if not self.channel_1.chat_element_by_text(image_description).is_element_displayed(60):
|
||||
self.channel_1.hide_keyboard_if_shown()
|
||||
self.channel_1.chat_element_by_text(image_description).image_in_message.click()
|
||||
self.home_1.just_fyi('Save image')
|
||||
self.channel_1.view_image_options_button.click()
|
||||
self.channel_1.save_image_icon_button.click()
|
||||
toast_element = self.channel_1.toast_content_element
|
||||
if toast_element.is_element_displayed():
|
||||
toast_element_text = toast_element.text
|
||||
if toast_element_text != self.channel_1.get_translation_by_key("photo-saved"):
|
||||
self.errors.append(
|
||||
"Shown message '%s' doesn't match expected '%s' after saving an image." % (
|
||||
toast_element_text, self.channel_1.get_translation_by_key("photo-saved")))
|
||||
else:
|
||||
self.errors.append("Message about saving a photo is not shown.")
|
||||
self.channel_1.click_system_back_button_until_element_is_shown(element="chat")
|
||||
|
||||
self.channel_1.just_fyi("Check that image is saved in gallery")
|
||||
self.channel_1.show_images_button.click()
|
||||
self.channel_1.allow_button.click_if_shown()
|
||||
if not self.channel_1.get_image_by_index(0).is_element_image_similar_to_template(
|
||||
"sauce_dark_image_gallery.png"):
|
||||
self.errors.append('Saved image is not shown in Recent')
|
||||
self.channel_1.click_system_back_button()
|
||||
|
||||
self.home_2.just_fyi('Check share option on opened image')
|
||||
self.channel_2.chat_element_by_text(image_description).image_in_message.click()
|
||||
self.channel_2.share_image_icon_button.click()
|
||||
self.channel_2.element_starts_with_text("Gmail").click()
|
||||
try:
|
||||
self.channel_2.wait_for_current_package_to_be('com.google.android.gm')
|
||||
except TimeoutException:
|
||||
self.errors.append("Can't share image")
|
||||
self.channel_2.click_system_back_button_until_element_is_shown(element="chat")
|
||||
|
||||
self.channel_2.just_fyi("Can reply to images")
|
||||
self.channel_2.quote_message(image_description)
|
||||
message_text = 'reply to image'
|
||||
self.channel_2.chat_message_input.send_keys(message_text)
|
||||
self.channel_2.send_message_button.click()
|
||||
chat_element_1 = self.channel_1.chat_element_by_text(message_text)
|
||||
if not chat_element_1.is_element_displayed(sec=60) or chat_element_1.replied_message_text != image_description:
|
||||
self.errors.append('Reply message was not received by the sender')
|
||||
self.channel_2.just_fyi("Set a reaction for the image message")
|
||||
self.channel_2.set_reaction(message=image_description)
|
||||
try:
|
||||
self.channel_1.chat_element_by_text(image_description).emojis_below_message().wait_for_element_text(1)
|
||||
except (Failed, NoSuchElementException):
|
||||
self.errors.append("Image message reaction is not shown for the sender")
|
||||
self.channel_1.just_fyi("Set a reaction for the message reply")
|
||||
self.channel_2.set_reaction(message=image_description, emoji="love")
|
||||
try:
|
||||
self.channel_2.chat_element_by_text(message_text).emojis_below_message(
|
||||
emoji="love").wait_for_element_text(1)
|
||||
except (Failed, NoSuchElementException):
|
||||
self.errors.append("Reply message reaction is not shown for the reply sender")
|
||||
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@marks.testrail_id(702840)
|
||||
def test_community_emoji_send_copy_paste_reply(self):
|
||||
emoji_name = random.choice(list(emoji.EMOJI_UNICODE))
|
||||
@@ -703,6 +722,10 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
|
||||
# 'subtitle': 'Twitter'
|
||||
# }
|
||||
}
|
||||
for home in self.home_1, self.home_2:
|
||||
if not home.chat_floating_screen.is_element_displayed():
|
||||
home.click_system_back_button_until_element_is_shown()
|
||||
home.get_to_community_channel_from_home(self.community_name)
|
||||
|
||||
for key, data in preview_urls.items():
|
||||
self.home_2.just_fyi("Checking %s preview case" % key)
|
||||
@@ -731,6 +754,9 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
|
||||
self.channel_1.just_fyi("Set reaction and check it")
|
||||
message_with_reaction = list(preview_urls.values())[-1]['url']
|
||||
self.channel_1.set_reaction(message=message_with_reaction, emoji="laugh")
|
||||
if not self.channel_2.chat_element_by_text(message_with_reaction).emojis_below_message(
|
||||
emoji="laugh").is_element_displayed(10):
|
||||
self.channel_2.hide_keyboard_if_shown()
|
||||
try:
|
||||
self.channel_2.chat_element_by_text(message_with_reaction).emojis_below_message(
|
||||
emoji="laugh").wait_for_element_text(1)
|
||||
@@ -743,6 +769,9 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
|
||||
def test_community_unread_messages_badge(self):
|
||||
self.channel_1.jump_to_communities_home()
|
||||
message = 'test message'
|
||||
if not self.home_2.chat_floating_screen.is_element_displayed():
|
||||
self.home_2.jump_to_communities_home()
|
||||
self.home_2.get_to_community_channel_from_home(self.community_name)
|
||||
self.channel_2.send_message(message)
|
||||
self.home_1.just_fyi('Check new messages badge is shown for community')
|
||||
community_element_1 = self.home_1.get_chat(self.community_name, community=True)
|
||||
@@ -817,6 +846,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
|
||||
self.home_2.just_fyi("Check that can send message in community after unblock")
|
||||
self.chat_2.send_message(message_unblocked)
|
||||
self.home_1.jump_to_card_by_text('# %s' % self.channel_name)
|
||||
self.chat_1.hide_keyboard_if_shown()
|
||||
if not self.chat_1.chat_element_by_text(message_unblocked).is_element_displayed(120):
|
||||
self.errors.append("%s was not received in public chat after user unblock!" % message_unblocked)
|
||||
self.errors.verify_no_errors()
|
||||
@@ -967,6 +997,6 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
|
||||
community.long_press_until_element_is_shown(community_to_leave.leave_community_button)
|
||||
community_to_leave.leave_community_button.click()
|
||||
community_to_leave.leave_community_button.click()
|
||||
if community.is_element_displayed():
|
||||
if not community.is_element_disappeared():
|
||||
self.errors.append('Community is still shown in the list after leave')
|
||||
self.errors.verify_no_errors()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import time
|
||||
|
||||
import base64
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
from appium.webdriver.common.touch_action import TouchAction
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from appium.webdriver.common.touch_action import TouchAction
|
||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException
|
||||
|
||||
from support.device_apps import start_web_browser
|
||||
@@ -130,7 +130,7 @@ class WalletButton(TabButton):
|
||||
|
||||
class ProfileButton(TabButton):
|
||||
def __init__(self, driver):
|
||||
super().__init__(driver, accessibility_id="open-profile")
|
||||
super().__init__(driver, accessibility_id="open-profile")
|
||||
|
||||
def navigate(self):
|
||||
from views.profile_view import ProfileView
|
||||
@@ -255,7 +255,8 @@ class BaseView(object):
|
||||
|
||||
# Floating screens (introduced by https://github.com/status-im/status-mobile/pull/16438)
|
||||
self.chat_floating_screen = BaseElement(self.driver, accessibility_id=":chat-floating-screen")
|
||||
self.community_floating_screen = BaseElement(self.driver, accessibility_id=":community-overview-floating-screen")
|
||||
self.community_floating_screen = BaseElement(self.driver,
|
||||
accessibility_id=":community-overview-floating-screen")
|
||||
|
||||
self.jump_to_button = Button(self.driver, accessibility_id="jump-to")
|
||||
|
||||
@@ -291,6 +292,7 @@ class BaseView(object):
|
||||
self.share_button = Button(self.driver, accessibility_id="share-my-contact-code-button")
|
||||
self.qr_code_image = Button(self.driver, accessibility_id="qr-code-image")
|
||||
self.sign_in_phrase = SignInPhraseText(self.driver)
|
||||
self.toast_content_element = BaseElement(self.driver, accessibility_id="toast-content")
|
||||
|
||||
# checkboxes and toggles
|
||||
self.checkbox_button = CheckBox(self.driver, accessibility_id="checkbox-off")
|
||||
@@ -303,7 +305,6 @@ class BaseView(object):
|
||||
self.airplane_mode_button = AirplaneModeButton(self.driver)
|
||||
self.enter_qr_edit_box = EnterQRcodeEditBox(self.driver)
|
||||
|
||||
|
||||
self.element_types = {
|
||||
'base': BaseElement,
|
||||
'button': Button,
|
||||
@@ -782,3 +783,12 @@ class BaseView(object):
|
||||
string_source = self.driver.page_source
|
||||
source = open(full_path_to_file, "a+")
|
||||
source.write(string_source)
|
||||
|
||||
def wait_for_current_package_to_be(self, expected_package_name: str, timeout: int = 10):
|
||||
start_time = time.time()
|
||||
while time.time() - start_time <= timeout:
|
||||
package = self.driver.current_package
|
||||
if package == expected_package_name:
|
||||
return
|
||||
time.sleep(1)
|
||||
raise TimeoutException("Driver current package is '%s' after %s seconds" % (package, timeout))
|
||||
|
||||
@@ -4,7 +4,6 @@ from datetime import datetime, timedelta
|
||||
from time import sleep
|
||||
|
||||
import dateutil.parser
|
||||
from appium.webdriver.common.mobileby import MobileBy
|
||||
from appium.webdriver.common.touch_action import TouchAction
|
||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException
|
||||
|
||||
@@ -284,13 +283,20 @@ class ChatElementByText(Text):
|
||||
except NoSuchElementException:
|
||||
self.driver.fail("No image is found in message!")
|
||||
|
||||
class ImageContainer(Button):
|
||||
def __init__(self, driver, parent_locator):
|
||||
super().__init__(driver, xpath='%s//*[@content-desc="image-container"]' % parent_locator)
|
||||
|
||||
def image_by_index(self, index: int):
|
||||
return BaseElement(self.driver, xpath="(%s//android.widget.ImageView)[%s]" % (self.locator, index))
|
||||
|
||||
@property
|
||||
def image_container_in_message(self):
|
||||
try:
|
||||
self.driver.info(
|
||||
"Trying to access images (image container) inside message with text '%s'" % self.message_text)
|
||||
ChatElementByText(self.driver, self.message_text).wait_for_sent_state(60)
|
||||
return Button(self.driver, xpath='%s//*[@content-desc="image-container"]' % self.locator)
|
||||
return self.ImageContainer(self.driver, self.locator)
|
||||
except NoSuchElementException:
|
||||
self.driver.fail("No image container is found in message!")
|
||||
|
||||
@@ -773,10 +779,9 @@ class ChatView(BaseView):
|
||||
self.recent_image_in_gallery = Button(self.driver,
|
||||
xpath="//*[contains(@resource-id,'thumbnail')]")
|
||||
self.cancel_send_image_button = Button(self.driver, accessibility_id="cancel-send-image")
|
||||
self.view_image_options = Button(self.driver,
|
||||
xpath="//*[@content-desc='icon']/android.widget.ImageView")
|
||||
self.share_image_icon_button = Button(self.driver, accessibility_id="share-button")
|
||||
self.save_image_icon_button = Button(self.driver, accessibility_id="save-button")
|
||||
self.share_image_icon_button = Button(self.driver, accessibility_id="share-image")
|
||||
self.view_image_options_button = Button(self.driver, accessibility_id="image-options")
|
||||
self.save_image_icon_button = Button(self.driver, accessibility_id="save-image")
|
||||
self.image_in_android_messenger = Button(self.driver, accessibility_id="Image")
|
||||
|
||||
# Audio
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 250 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
@@ -332,6 +332,8 @@ class HomeView(BaseView):
|
||||
return chat_element
|
||||
|
||||
def handle_contact_request(self, username: str, action='accept'):
|
||||
if self.toast_content_element.is_element_displayed(10):
|
||||
self.toast_content_element.wait_for_invisibility_of_element()
|
||||
if self.notifications_unread_badge.is_element_displayed(30):
|
||||
self.open_activity_center_button.click()
|
||||
chat_element = ActivityCenterElement(self.driver, username[:25])
|
||||
|
||||
@@ -1041,7 +1041,6 @@
|
||||
"remote-notifications": "Remote notifications",
|
||||
"remote-notifications-subtitle": "Enable google push notifications",
|
||||
"show-notifications": "Show notifications",
|
||||
"notification-settings": "Notifications",
|
||||
"notification-settings": "Notification settings",
|
||||
"notifications-servers": "Notification servers",
|
||||
"notifications-preferences": "Notification preferences",
|
||||
@@ -2209,11 +2208,9 @@
|
||||
"read-more": "Read more",
|
||||
"token-gated-communities-info": "Here will be something relevant about this topic. This will help the user get more context and therefore have a better understanding of it.",
|
||||
"dont-yell-at-me": "Don’t yell at me",
|
||||
"view-channel-members-and-details": "View channel members and details",
|
||||
"unmute-channel": "Unmute channel",
|
||||
"share-channel": "Share channel",
|
||||
"all-messages": "All messages",
|
||||
"muted-until": "Muted until",
|
||||
"muted-until": "Muted until {{duration}}",
|
||||
"until-you-turn-it-back-on": "you turn it back on",
|
||||
"mon": "Mon",
|
||||
"tue": "Tue",
|
||||
@@ -2247,5 +2244,6 @@
|
||||
"until": "until",
|
||||
"chat-unmuted-successfully": "Chat unmuted successfully! ",
|
||||
"channel-unmuted-successfully": "Channel unmuted successfully! ",
|
||||
"photo-saved": "Photo saved to your device"
|
||||
"photo-saved": "Photo saved to your device",
|
||||
"community-unmuted": "Community unmuted"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user