Anul 3 Semestrul 1
@@ -77,3 +77,131 @@ __pycache__/
|
||||
*.gcno
|
||||
*.gcda
|
||||
|
||||
node_modules
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# .NET Core
|
||||
bin/
|
||||
obj/
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Auto-generated files
|
||||
*.generated.cs
|
||||
*.generated.ts
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
|
||||
# Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# User-specific files (Mono Auto Generated)
|
||||
mono_crash.*
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# VS Code directories
|
||||
.vscode/
|
||||
.history/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
/.vs
|
||||
/WebApi/.vs
|
||||
/WebApi/.vs/WebApi/CopilotIndices/0.2.1657.32929
|
||||
/WebApi/.vs/WebApi/FileContentIndex
|
||||
/WebApi/.vs/WebApi/copilot-chat/6ceaa888/sessions
|
||||
/WebApi/.vs/ProjectEvaluation
|
||||
/WebApi/.vs/WebApi/DesignTimeBuild
|
||||
/WebApi/.vs/WebApi/v17
|
||||
|
||||
|
||||
/WebApi/AIModels
|
||||
*.onnx
|
||||
.DS_Store
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
devel/
|
||||
build/
|
||||
__pycache__/
|
||||
.env
|
||||
experiments/
|
||||
@@ -0,0 +1,22 @@
|
||||
q0 q1 q2 q3 qf
|
||||
1 2 3
|
||||
q0
|
||||
qf
|
||||
q0 q0 1
|
||||
q0 q0 2
|
||||
q0 q0 3
|
||||
q0 q1 1
|
||||
q0 q2 2
|
||||
q0 q3 3
|
||||
q1 q1 1
|
||||
q1 q1 2
|
||||
q1 q1 3
|
||||
q1 qf 1
|
||||
q2 q2 1
|
||||
q2 q2 2
|
||||
q2 q2 3
|
||||
q2 qf 2
|
||||
q3 q3 1
|
||||
q3 q3 2
|
||||
q3 q3 3
|
||||
q3 qf 3
|
||||
@@ -0,0 +1,46 @@
|
||||
class FiniteAutomata:
|
||||
def __init__(self, file_path):
|
||||
self.alphabet = set()
|
||||
self.states = set()
|
||||
self.transitions = {}
|
||||
self.start_state = None
|
||||
self.final_states = set()
|
||||
self.read_fa(file_path)
|
||||
|
||||
def read_fa(self, file_path):
|
||||
with open(file_path, "r") as file:
|
||||
self.states = set(file.readline().strip().split())
|
||||
self.alphabet = set(file.readline().strip().split())
|
||||
self.start_state = file.readline().strip()
|
||||
self.final_states = set(file.readline().strip().split())
|
||||
for line in file:
|
||||
state1, state2, char = line.strip().split()
|
||||
if state1 not in self.transitions:
|
||||
self.transitions[state1] = {}
|
||||
if char not in self.transitions[state1]:
|
||||
self.transitions[state1][char] = []
|
||||
self.transitions[state1][char].append(state2)
|
||||
|
||||
def is_accepted(self, word):
|
||||
current_states = [self.start_state]
|
||||
for char in word:
|
||||
new_states = []
|
||||
for state in current_states:
|
||||
if state in self.transitions and char in self.transitions[state]:
|
||||
new_states.extend(self.transitions[state][char])
|
||||
current_states = new_states
|
||||
return any(state in self.final_states for state in current_states)
|
||||
|
||||
def isNFA(self):
|
||||
for state in self.transitions:
|
||||
for char in self.transitions[state]:
|
||||
if len(self.transitions[state][char]) > 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
fa = FiniteAutomata("FA.in")
|
||||
print(fa.isNFA())
|
||||
print(fa.is_accepted("1312"))
|
||||
print(fa.is_accepted("12321"))
|
||||
print(fa.is_accepted(""))
|
||||
@@ -0,0 +1,161 @@
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from Parser import RecursiveDescentParser
|
||||
from ParserOutput import TreeNode, ParserOutput
|
||||
|
||||
|
||||
class Grammar:
|
||||
def __init__(self):
|
||||
self.nonterminals = set()
|
||||
self.terminals = set()
|
||||
self.productions = defaultdict(list)
|
||||
self.start_symbol = None
|
||||
|
||||
def read_from_file(self, filename):
|
||||
|
||||
with open(filename, 'r') as file:
|
||||
lines = file.readlines()
|
||||
|
||||
|
||||
grammar_header = lines[0].strip()
|
||||
match = re.match(r"G\s*=\s*\(\s*\{([^}]*)\}\s*,\s*\{([^}]*)\}\s*,\s*P\s*,\s*([^)]+)\s*\)", grammar_header)
|
||||
|
||||
if not match:
|
||||
raise ValueError("Invalid grammar format in the first line.")
|
||||
|
||||
self.nonterminals = set([x.strip() for x in set(match.group(1).split(', '))])
|
||||
self.terminals = set([x.strip() for x in set(match.group(2).split(', '))])
|
||||
self.start_symbol = match.group(3)
|
||||
|
||||
# Parse the productions
|
||||
for line in lines[1:]:
|
||||
line = line.strip()
|
||||
if line.startswith("P :"):
|
||||
line = line[3:].strip()
|
||||
if "->" in line:
|
||||
lhs, rhs = line.split('->')
|
||||
lhs = lhs.strip()
|
||||
self.nonterminals.add(lhs)
|
||||
for production in rhs.split('|'):
|
||||
production = production.strip()
|
||||
self.productions[lhs].append(production.strip())
|
||||
|
||||
def print_nonterminals(self):
|
||||
print("Nonterminals:", self.nonterminals)
|
||||
|
||||
def print_terminals(self):
|
||||
print("Terminals:", self.terminals)
|
||||
|
||||
def print_productions(self):
|
||||
for lhs, rhs in self.productions.items():
|
||||
print(f"{lhs} -> {' | '.join(rhs)}")
|
||||
|
||||
def get_productions_for(self, nonterminal):
|
||||
return self.productions.get(nonterminal, [])
|
||||
|
||||
def is_cfg(self):
|
||||
for lhs, rhs_list in self.productions.items():
|
||||
|
||||
if lhs not in self.nonterminals:
|
||||
print(f"Invalid nonterminal: {lhs}")
|
||||
return False
|
||||
|
||||
for rhs in rhs_list:
|
||||
rhs_symbols = rhs.split()
|
||||
|
||||
for symbol in rhs_symbols:
|
||||
if symbol not in self.terminals and symbol not in self.nonterminals:
|
||||
print(f"Invalid symbol in RHS: {symbol}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def menu():
|
||||
g = Grammar()
|
||||
#g.read_from_file("g2.txt")
|
||||
parser = None
|
||||
while True:
|
||||
print("1. Read grammar from file")
|
||||
print("2. Print nonterminals")
|
||||
print("3. Print terminals")
|
||||
print("4. Print productions")
|
||||
print("5. Get productions for a nonterminal")
|
||||
print("6. Check if the grammar is CFG")
|
||||
print("7. Parse input string")
|
||||
print("0. Exit")
|
||||
input_command = input("Enter your command: ")
|
||||
match input_command:
|
||||
case "1":
|
||||
filename = input("Enter the filename: ")
|
||||
g.read_from_file(filename)
|
||||
case "2":
|
||||
g.print_nonterminals()
|
||||
case "3":
|
||||
g.print_terminals()
|
||||
case "4":
|
||||
g.print_productions()
|
||||
case "5":
|
||||
nonterminal = input("Enter the nonterminal: ")
|
||||
print(g.get_productions_for(nonterminal))
|
||||
case "6":
|
||||
print(g.is_cfg())
|
||||
case "7":
|
||||
input_string = input("Enter the input string: ")
|
||||
parser = RecursiveDescentParser(g, input_string)
|
||||
if parser.parse():
|
||||
print(parser.print_parse_tree())
|
||||
parser.save_parse_tree("out1.txt")
|
||||
|
||||
else:
|
||||
print("The input string is not in the language")
|
||||
print(parser.stack)
|
||||
print(parser.parsed_stack)
|
||||
case "0":
|
||||
return
|
||||
case _:
|
||||
print("Invalid command")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
menu()
|
||||
# g = Grammar()
|
||||
# g.read_from_file("g1.txt")
|
||||
# parser = RecursiveDescentParser(g, "a a b")
|
||||
# parser.expand("S", 0)
|
||||
# print(parser.current_pos)
|
||||
# print(parser.stack)
|
||||
# print(parser.parsed_stack)
|
||||
# parser.advance()
|
||||
# print(parser.current_pos)
|
||||
# print(parser.stack)
|
||||
# print(parser.parsed_stack)
|
||||
# parser.expand("A", 1)
|
||||
# print(parser.current_pos)
|
||||
# print(parser.stack)
|
||||
# print(parser.parsed_stack)
|
||||
# parser.advance()
|
||||
# print(parser.current_pos)
|
||||
# print(parser.stack)
|
||||
# print(parser.parsed_stack)
|
||||
# parser.back()
|
||||
# print(parser.current_pos)
|
||||
# print(parser.stack)
|
||||
# print(parser.parsed_stack)
|
||||
# parser.another_try("A",1)
|
||||
# print(parser.current_pos)
|
||||
# print(parser.stack)
|
||||
# print(parser.parsed_stack)
|
||||
# parser.advance()
|
||||
# print(parser.current_pos)
|
||||
# print(parser.stack)
|
||||
# print(parser.parsed_stack)
|
||||
# parser.advance()
|
||||
# print(parser.current_pos)
|
||||
# print(parser.stack)
|
||||
# print(parser.parsed_stack)
|
||||
# print(parser.success())
|
||||
# print(parser.current_pos)
|
||||
# print(parser.stack)
|
||||
# print(parser.parsed_stack)
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import copy
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, key, value):
|
||||
self.key = key
|
||||
self.value = value
|
||||
self.next = None
|
||||
|
||||
|
||||
class HashTable:
|
||||
def __init__(self):
|
||||
self.capacity = 2
|
||||
self.noOfElements = 0
|
||||
self.elementList = [None] * 2
|
||||
|
||||
def hash(self, value):
|
||||
if isinstance(value, int):
|
||||
return value % self.capacity
|
||||
|
||||
sum = 0
|
||||
for l in value:
|
||||
sum += ord(l)
|
||||
return sum % self.capacity
|
||||
|
||||
def insert(self, key, value):
|
||||
if self.noOfElements and (self.capacity // self.noOfElements < 2):
|
||||
self.resizeAndRehash()
|
||||
hash_key = self.hash(key)
|
||||
element = self.elementList[hash_key]
|
||||
|
||||
if element is None:
|
||||
self.elementList[hash_key] = Node(key, value)
|
||||
self.noOfElements += 1
|
||||
return
|
||||
|
||||
while element.next is not None:
|
||||
element = element.next
|
||||
element.next = Node(key, value)
|
||||
self.noOfElements += 1
|
||||
|
||||
def get(self, key):
|
||||
element = self.elementList[self.hash(key)]
|
||||
while element is not None:
|
||||
if element.key == key:
|
||||
return element.value
|
||||
element = element.next
|
||||
return None
|
||||
|
||||
def getPosition(self, key):
|
||||
element = self.elementList[self.hash(key)]
|
||||
position = 0
|
||||
while element is not None:
|
||||
if element.key == key:
|
||||
return (key,position)
|
||||
element = element.next
|
||||
position += 1
|
||||
return None
|
||||
|
||||
def resizeAndRehash(self):
|
||||
self.capacity *= 2
|
||||
|
||||
copyElementList = copy.deepcopy(self.elementList)
|
||||
self.elementList = [None] * self.capacity
|
||||
self.noOfElements = 0
|
||||
for element in copyElementList:
|
||||
copyElement = copy.deepcopy(element)
|
||||
while copyElement is not None:
|
||||
self.insert(copyElement.key, copyElement.value)
|
||||
copyElement = copyElement.next
|
||||
|
||||
def __str__(self):
|
||||
var = ""
|
||||
for element in self.elementList:
|
||||
while element is not None:
|
||||
var += f"{element.key} -> {element.value}\n"
|
||||
element = element.next
|
||||
return var
|
||||
@@ -0,0 +1,69 @@
|
||||
class Main{
|
||||
void entry Main(){
|
||||
Problem1([3,5,7,2])
|
||||
Problem2([3,5,7,2])
|
||||
int[] x = [3,5,7,2]
|
||||
Problem3(x,[8,4,2,8])
|
||||
|
||||
}
|
||||
|
||||
int Problem1(int[] numbers){
|
||||
int max = -1;
|
||||
int index = 0;
|
||||
while(index < numbers.lenght){
|
||||
int number = numbers[index];
|
||||
if(number > max){
|
||||
max = number;
|
||||
}
|
||||
index = index + 1;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
int Problem2(int[] numbers){
|
||||
var prod = 0;
|
||||
int index = 0;
|
||||
while(index < numbers.lenght){
|
||||
int number = numbers[index];
|
||||
prod *= number;
|
||||
}
|
||||
index = index + 1;
|
||||
return prod;
|
||||
}
|
||||
|
||||
void Problem3(int[] numbers1, int[] numbers2){
|
||||
while(index < numbers.lenght){
|
||||
numbers1[index] = numbers1[index] + numbers2[index];
|
||||
index = index + 1;
|
||||
}
|
||||
}
|
||||
Problem1err(number, power){
|
||||
return number ** power
|
||||
}
|
||||
}
|
||||
|
||||
// Key Features of the Language:
|
||||
|
||||
// OOP:
|
||||
// The programming language supports classes and object. Everything written must belong to a class
|
||||
|
||||
// Entry Point:
|
||||
|
||||
// The entry point of the program is defined by a method with the keyword entry.
|
||||
|
||||
// Method Definitions:
|
||||
|
||||
// Methods are defined using modifiers such as public and return types like int or var.
|
||||
|
||||
// Dynamic Typing and Static Typing:
|
||||
|
||||
// var is used for variables where the type is dynamically inferred, similar to languages like Python. This suggests a flexible type system where variables can hold different types based on context. It can however also support static typing
|
||||
|
||||
|
||||
// Loops and Iteration:
|
||||
|
||||
// The foreach loop is used to iterate over collections, such as lists. The syntax is familiar to C# or Python-style iteration.
|
||||
|
||||
// Lexical Rules:
|
||||
|
||||
// Functions are declared with a return type (must be present even if void or dynamic), followed by the function name, and the parameter list. The parameter list can have strong types (like List<int>) or flexible types (like var), but must contain a type. Every statement must be followed by a semi-colon (;), and code is separated into blocks using { and }
|
||||
@@ -0,0 +1,103 @@
|
||||
from ParserOutput import ParserOutput, TreeNode
|
||||
|
||||
class RecursiveDescentParser:
|
||||
def __init__(self, grammar, input_string):
|
||||
self.grammar = grammar
|
||||
self.input = input_string.split()
|
||||
self.current_pos = 0
|
||||
self.stack = [] # Stack for backtracking
|
||||
self.parsed_stack = [] # Current sequence of symbols being parsed
|
||||
self.production_sequence = [] # To track applied productions
|
||||
self.parse_tree = ParserOutput(self.grammar.terminals, self.grammar.nonterminals) # Parse tree output
|
||||
|
||||
def expand(self, nonterminal, prod_index=0):
|
||||
"""Expand a nonterminal using the specified production index."""
|
||||
if nonterminal in self.grammar.productions:
|
||||
production = self.grammar.productions[nonterminal][prod_index]
|
||||
self.stack.append((nonterminal, prod_index, self.current_pos, len(self.parsed_stack))) # Push state
|
||||
self.production_sequence.append((nonterminal, production)) # Track production
|
||||
self.parsed_stack = production.split() + self.parsed_stack # Add production to parsed stack
|
||||
else:
|
||||
raise ValueError(f"No production for {nonterminal}")
|
||||
|
||||
def advance(self):
|
||||
if self.current_pos < len(self.input) and self.parsed_stack:
|
||||
next_symbol = self.parsed_stack.pop(0)
|
||||
if next_symbol == self.input[self.current_pos]:
|
||||
self.current_pos += 1
|
||||
return True
|
||||
else:
|
||||
self.parsed_stack.insert(0, next_symbol) # Push it back
|
||||
return False
|
||||
return False
|
||||
|
||||
def back(self):
|
||||
if self.stack:
|
||||
# Pop the last saved state
|
||||
nonterminal, prod_index, prev_pos, stack_size = self.stack.pop()
|
||||
|
||||
# Restore the previous position in the input
|
||||
self.current_pos = prev_pos
|
||||
|
||||
# Restore the stack to the state it was before expanding the production
|
||||
self.parsed_stack = self.parsed_stack[:stack_size]
|
||||
self.production_sequence.pop()
|
||||
|
||||
# Try the next production for the same nonterminal
|
||||
return self.another_try(nonterminal, prod_index)
|
||||
|
||||
# If the stack is empty, backtracking is not possible
|
||||
return False
|
||||
|
||||
def another_try(self, nonterminal, prod_index):
|
||||
"""Try the next production of the nonterminal."""
|
||||
prod_list = self.grammar.productions.get(nonterminal, [])
|
||||
if prod_index + 1 < len(prod_list):
|
||||
self.expand(nonterminal, prod_index + 1) # Expand using the next production
|
||||
return True
|
||||
return False
|
||||
|
||||
def success(self):
|
||||
"""Check if parsing succeeded."""
|
||||
return self.current_pos == len(self.input) and not self.parsed_stack
|
||||
|
||||
def parse(self):
|
||||
if not self.grammar.start_symbol:
|
||||
raise ValueError("Grammar must have a start symbol defined.")
|
||||
|
||||
self.expand(self.grammar.start_symbol)
|
||||
|
||||
while True:
|
||||
# If parsing succeeds
|
||||
if self.success():
|
||||
self.parse_tree.build_tree(self.production_sequence)
|
||||
return True
|
||||
# If there's something to parse
|
||||
if self.parsed_stack:
|
||||
next_symbol = self.parsed_stack[0]
|
||||
|
||||
# If next symbol is a terminal, try to match it
|
||||
if next_symbol in self.grammar.terminals:
|
||||
if not self.advance():
|
||||
# If matching fails, backtrack
|
||||
if not self.back() and len(self.stack) <= 1:
|
||||
return False
|
||||
elif next_symbol in self.grammar.nonterminals:
|
||||
# Expand nonterminal
|
||||
self.parsed_stack.pop(0)
|
||||
self.expand(next_symbol)
|
||||
else:
|
||||
# Handle unexpected symbols
|
||||
print(f"Error: Unexpected symbol {next_symbol}")
|
||||
return False
|
||||
else:
|
||||
if not self.back() and len(self.stack) <= 1:
|
||||
return False
|
||||
|
||||
def print_parse_tree(self):
|
||||
"""Prints the parse tree to the screen."""
|
||||
self.parse_tree.print_to_screen()
|
||||
|
||||
def save_parse_tree(self, filename):
|
||||
"""Saves the parse tree representation to a file."""
|
||||
self.parse_tree.save_to_file(filename)
|
||||
@@ -0,0 +1,111 @@
|
||||
import string
|
||||
|
||||
|
||||
class TreeNode:
|
||||
def __init__(self, value, father=None):
|
||||
self.value = value # Grammar symbol (terminal/nonterminal)
|
||||
self.father = father # Parent node
|
||||
self.sibling = None # Sibling node
|
||||
self.children = [] # List of children for easy access
|
||||
|
||||
def add_child(self, child):
|
||||
"""Adds a child to the current node."""
|
||||
if self.children:
|
||||
# Set sibling for the last child
|
||||
self.children[-1].sibling = child
|
||||
self.children.append(child)
|
||||
|
||||
def __str__(self):
|
||||
"""String representation of the node."""
|
||||
return self.value
|
||||
|
||||
|
||||
class ParserOutput:
|
||||
def __init__(self, terminal, nonterminal):
|
||||
self.root = None # Root of the parse tree
|
||||
self.terminal = terminal
|
||||
self.nonterminal = nonterminal
|
||||
|
||||
def build_tree(self, production_sequence):
|
||||
"""
|
||||
Builds the parse tree from a sequence of productions.
|
||||
:param production_sequence: List of (nonterminal, production) tuples
|
||||
"""
|
||||
if not production_sequence:
|
||||
return None
|
||||
|
||||
stack = [] # Stack for constructing the tree
|
||||
for nonterminal, production in production_sequence:
|
||||
if not self.root:
|
||||
# Create the root node
|
||||
self.root = TreeNode(nonterminal)
|
||||
stack.append(self.root)
|
||||
current = stack.pop()
|
||||
symbols = production.split()
|
||||
for symbol in symbols:
|
||||
child = TreeNode(symbol, current)
|
||||
current.add_child(child)
|
||||
if symbol in self.nonterminal: # Nonterminal
|
||||
stack.append(child)
|
||||
else:
|
||||
# Create nodes based on production
|
||||
current = stack.pop()
|
||||
symbols = production.split()
|
||||
for symbol in symbols:
|
||||
child = TreeNode(symbol, current)
|
||||
current.add_child(child)
|
||||
if symbol in self.nonterminal: # Nonterminal
|
||||
stack.append(child)
|
||||
|
||||
def transform_representation(self, node=None, level=0):
|
||||
if node is None:
|
||||
node = self.root
|
||||
|
||||
result = "-" * level + str(node) + "\n"
|
||||
for child in node.children:
|
||||
result += self.transform_representation(child, level + 1)
|
||||
return result
|
||||
|
||||
def print_to_screen(self):
|
||||
print(self.print_table())
|
||||
|
||||
def print_table(self):
|
||||
"""
|
||||
Prints a table representation of the parse tree.
|
||||
"""
|
||||
rows = []
|
||||
index_map = {}
|
||||
|
||||
def traverse(node, index=1, parent_index=None):
|
||||
current_index = len(rows)
|
||||
index_map[node] = current_index
|
||||
|
||||
# Determine right sibling
|
||||
sibling_index = None
|
||||
if node.sibling:
|
||||
sibling_index = len(rows) + 1 # Right sibling will be the next node added
|
||||
|
||||
rows.append((current_index, node.value, parent_index, sibling_index))
|
||||
|
||||
for child in node.children:
|
||||
traverse(child, len(rows), current_index)
|
||||
|
||||
traverse(self.root)
|
||||
|
||||
# Print the table
|
||||
s = ""
|
||||
s += "+-------+-------+--------+---------------+" + "\n"
|
||||
s += "| Index | Value | Parent | Right Sibling |" + "\n"
|
||||
s += "+-------+-------+--------+---------------+" + "\n"
|
||||
for index, value, parent, sibling in rows:
|
||||
s += f"| {index:<3} | {value:<3} | {parent + 1 if parent is not None else '0':<4} | {sibling if sibling is not None else '0':<11} |" + "\n"
|
||||
|
||||
s += "+-------+-------+--------+---------------+" + "\n"
|
||||
return s
|
||||
|
||||
|
||||
def save_to_file(self, filename):
|
||||
with open(filename, 'w') as file:
|
||||
file.write(self.print_table())
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
from operator import le
|
||||
from SymbolTable import SymbolTable
|
||||
from Hashtable import HashTable
|
||||
import re
|
||||
|
||||
class Scanner:
|
||||
def __init__(self, tokensPath):
|
||||
self.tokensPath = tokensPath
|
||||
self.symbolTable = SymbolTable()
|
||||
self.indentifierRegex = r"^[a-zA-Z][a-zA-Z0-9]*$"
|
||||
self.constantRegex = r"^0$|^[1-9][0-9]*$|^-[1-9][0-9]*$"
|
||||
self.tokens = []
|
||||
self.pif = {}
|
||||
self.readTokens()
|
||||
|
||||
def readTokens(self):
|
||||
with open(self.tokensPath, "r") as file:
|
||||
isKeyword = True
|
||||
isOperator = False
|
||||
for line in file:
|
||||
self.tokens.append(line.strip())
|
||||
self.tokensRegex = '|'.join(map(re.escape, self.tokens))
|
||||
|
||||
def isIdentifier(self, token):
|
||||
return re.match(self.indentifierRegex, token)
|
||||
|
||||
def isConstant(self, token):
|
||||
return re.match(self.constantRegex, token)
|
||||
|
||||
def isToken(self, token):
|
||||
return token in self.tokens
|
||||
|
||||
def scan(self, codePath):
|
||||
with open(codePath, "r") as file:
|
||||
identifierCounter = 1
|
||||
constantCounter = 1
|
||||
lineCounter = 0
|
||||
try:
|
||||
for line in file:
|
||||
line = line.strip()
|
||||
tokens = re.findall(self.tokensRegex + r"|\w+", line)
|
||||
for token in tokens:
|
||||
if self.isToken(token):
|
||||
self.pif[token] = -1
|
||||
elif self.isIdentifier(token):
|
||||
position = self.symbolTable.getPositionIdentifier(token)
|
||||
if position is None:
|
||||
self.symbolTable.addIdentifier(token, identifierCounter)
|
||||
self.pif["Identifier"] = identifierCounter
|
||||
identifierCounter += 1
|
||||
else:
|
||||
self.pif["Identifier"] = position[1]
|
||||
elif self.isConstant(token):
|
||||
position = self.symbolTable.getPositionConstants(token)
|
||||
if position is None:
|
||||
self.symbolTable.addConstants(token, constantCounter)
|
||||
self.pif['Constant'] = constantCounter
|
||||
constantCounter += 1
|
||||
else:
|
||||
self.pif["Constant"] = position[1]
|
||||
else:
|
||||
raise Exception(f"Invalid token {token} at line {lineCounter}")
|
||||
lineCounter += 1
|
||||
print("Lexically correct")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
|
||||
|
||||
def main():
|
||||
scanner = Scanner("tokens.in")
|
||||
scanner.scan("p1error.in")
|
||||
#save output to file
|
||||
with open("PIF.out", "w") as file:
|
||||
for key in scanner.pif:
|
||||
file.write(f"{key} -> {scanner.pif[key]}\n")
|
||||
|
||||
with open("ST.out", "w") as file:
|
||||
file.write(str(scanner.symbolTable))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from Hashtable import HashTable
|
||||
|
||||
|
||||
class SymbolTable:
|
||||
def __init__(self):
|
||||
self.symboltableConstants = HashTable()
|
||||
self.symboltableIdentifier = HashTable()
|
||||
|
||||
def addIdentifier(self, identifier, value):
|
||||
self.symboltableIdentifier.insert(identifier, value)
|
||||
|
||||
def getIdentifier(self, identifier):
|
||||
return self.symboltableIdentifier.get(identifier)
|
||||
|
||||
def getPositionIdentifier(self, identifier):
|
||||
return self.symboltableIdentifier.getPosition(identifier)
|
||||
|
||||
def addConstants(self, constants, value):
|
||||
self.symboltableConstants.insert(constants, value)
|
||||
|
||||
def getConstants(self, constants):
|
||||
return self.symboltableConstants.get(constants)
|
||||
|
||||
def getPositionConstants(self, constants):
|
||||
return self.symboltableConstants.getPosition(constants)
|
||||
|
||||
def __str__(self):
|
||||
var = "Identifier Table\n"
|
||||
for i in range(self.symboltableIdentifier.capacity):
|
||||
element = self.symboltableIdentifier.elementList[i]
|
||||
while element is not None:
|
||||
var += str(element.key) + " " + str(element.value) + "\n"
|
||||
element = element.next
|
||||
var += "Constants Table\n"
|
||||
for i in range(self.symboltableConstants.capacity):
|
||||
element = self.symboltableConstants.elementList[i]
|
||||
while element is not None:
|
||||
var += str(element.key) + " " + str(element.value) + "\n"
|
||||
element = element.next
|
||||
return var
|
||||
@@ -0,0 +1,4 @@
|
||||
G = ({S, A}, {a, b}, P, S)
|
||||
P :
|
||||
S -> a A
|
||||
A -> a | b | a A | b A
|
||||
@@ -0,0 +1,40 @@
|
||||
G = ({program, statement_list, statement, assignment_stmt, input_stmt, output_stmt, if_stmt, for_stmt, array_decl_stmt, condition, increment_stmt, expression, term, factor, relational_operator, binary_operator, concatenation},{=, (, ), [, ], if, else, for, ARRAY, echo, readline, +, -, *, /, <, <=, ==, >=, !=, ++, --, :, ;, IDENTIFIER, INTEGER_CONST, FLOAT_CONST, STRING_CONST},P, program)
|
||||
|
||||
P :
|
||||
program -> statement_list
|
||||
|
||||
statement_list -> statement | statement statement_list
|
||||
|
||||
statement -> assignment_stmt | input_stmt | output_stmt | if_stmt | for_stmt | array_decl_stmt
|
||||
|
||||
assignment_stmt -> IDENTIFIER = expression
|
||||
|
||||
input_stmt -> IDENTIFIER = readline_function ( STRING_CONST )
|
||||
|
||||
output_stmt -> echo_function ( STRING_CONST concatenation IDENTIFIER )
|
||||
|
||||
if_stmt -> if ( condition ) : statement | if ( condition ) : statement else : statement
|
||||
|
||||
for_stmt -> for ( assignment_stmt ; condition ; increment_stmt ) : statement
|
||||
|
||||
array_decl_stmt -> IDENTIFIER = ARRAY [ INTEGER_CONST ]
|
||||
|
||||
condition -> expression relational_operator expression
|
||||
|
||||
increment_stmt -> IDENTIFIER ++ | IDENTIFIER --
|
||||
|
||||
expression -> term | term binary_operator expression
|
||||
|
||||
term -> factor | factor binary_operator term
|
||||
|
||||
factor -> IDENTIFIER | INTEGER_CONST | FLOAT_CONST | STRING_CONST
|
||||
|
||||
relational_operator -> < | <= | == | >= | !=
|
||||
|
||||
binary_operator -> + | - | * | /
|
||||
|
||||
concatenation -> +
|
||||
|
||||
echo_function -> echo
|
||||
|
||||
readline_function -> readline
|
||||
@@ -0,0 +1,112 @@
|
||||
%{
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "lang.tab.h"
|
||||
|
||||
// Define YYSTYPE structure
|
||||
|
||||
|
||||
extern YYSTYPE yylval;
|
||||
extern int yydebug; // Declare yydebug
|
||||
// Token definitions
|
||||
|
||||
|
||||
// File pointers
|
||||
FILE *yyin, *yyout;
|
||||
|
||||
%}
|
||||
|
||||
%%
|
||||
|
||||
"int"|"char"|"void" { fprintf(yyout, "SIMPLETYPE\n"); return SIMPLETYPE; }
|
||||
"class" { fprintf(yyout, "CLASS\n"); return CLASS; }
|
||||
"entry" { fprintf(yyout, "ENTRY\n"); return ENTRY; }
|
||||
"if" { fprintf(yyout, "IF\n"); return IF; }
|
||||
"while" { fprintf(yyout, "WHILE\n"); return WHILE; }
|
||||
"write" { fprintf(yyout, "WRITE\n"); return WRITE; }
|
||||
"read" { fprintf(yyout, "READ\n"); return READ; }
|
||||
"return" { fprintf(yyout, "RETURN\n"); return RETURN; }
|
||||
"{" { fprintf(yyout, "LBRACE\n"); return LBRACE; }
|
||||
"}" { fprintf(yyout, "RBRACE\n"); return RBRACE; }
|
||||
"(" { fprintf(yyout, "LPAREN\n"); return LPAREN; }
|
||||
")" { fprintf(yyout, "RPAREN\n"); return RPAREN; }
|
||||
"[" { fprintf(yyout, "LBRACKET\n"); return LBRACKET; }
|
||||
"]" { fprintf(yyout, "RBRACKET\n"); return RBRACKET; }
|
||||
"+" { fprintf(yyout, "PLUS\n"); return PLUS; }
|
||||
"-" { fprintf(yyout, "MINUS\n"); return MINUS; }
|
||||
"*" { fprintf(yyout, "MULT\n"); return MULT; }
|
||||
"/" { fprintf(yyout, "DIV\n"); return DIV; }
|
||||
"=" { fprintf(yyout, "ASSIGN\n"); return ASSIGN; }
|
||||
"==" { fprintf(yyout, "EQ\n"); return EQ; }
|
||||
"<" { fprintf(yyout, "LT\n"); return LT; }
|
||||
"<=" { fprintf(yyout, "LE\n"); return LE; }
|
||||
">" { fprintf(yyout, "GT\n"); return GT; }
|
||||
">=" { fprintf(yyout, "GE\n"); return GE; }
|
||||
";" { fprintf(yyout, "SEMICOLON\n"); return SEMICOLON; }
|
||||
"." { fprintf(yyout, "DOT\n"); return DOT; }
|
||||
"," { fprintf(yyout, "COMMA\n"); return COMMA; }
|
||||
[0-9]+ { yylval.intval = atoi(yytext); fprintf(yyout, "CONSTANTEXP %d\n", yylval.intval); return CONSTANTEXP; }
|
||||
[a-zA-Z_][a-zA-Z0-9_]* { yylval.strval = strdup(yytext); fprintf(yyout, "IDENTIFIER %s\n", yylval.strval); return IDENTIFIER; }
|
||||
[ \t\n]+ { /* Ignore spaces and tabs */ }
|
||||
\r\n { /* Increment line count if needed */ }
|
||||
. { fprintf(yyout, "Unknown character: %s\n", yytext); }
|
||||
|
||||
%%
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
// Open input and output files
|
||||
yyin = fopen(argv[1], "r");
|
||||
if (!yyin) {
|
||||
perror("Failed to open input file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
yyout = fopen(argv[2], "w");
|
||||
if (!yyout) {
|
||||
perror("Failed to open output file");
|
||||
fclose(yyin);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int token;
|
||||
while ((token = yylex()) != 0) {
|
||||
// Process tokens
|
||||
}
|
||||
fclose(yyin);
|
||||
fclose(yyout);
|
||||
yyin = fopen(argv[1], "r");
|
||||
if (!yyin) {
|
||||
perror("Failed to open input file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
yyout = stdout;
|
||||
if (!yyout) {
|
||||
perror("Failed to open output file");
|
||||
fclose(yyin);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Close files
|
||||
// printf("%d\n", yylex());
|
||||
// printf("%d\n", yylex());
|
||||
// printf("%d\n", yylex());
|
||||
// printf("%d\n", yylex());
|
||||
|
||||
yydebug = 1;
|
||||
if (yyparse() == 0) {
|
||||
printf("Parsing successful\n");
|
||||
} else {
|
||||
printf("Parsing failed\n");
|
||||
}
|
||||
fclose(yyin);
|
||||
fclose(yyout);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/* A Bison parser, made by GNU Bison 3.8.2. */
|
||||
|
||||
/* Bison interface for Yacc-like parsers in C
|
||||
|
||||
Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
|
||||
Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* As a special exception, you may create a larger work that contains
|
||||
part or all of the Bison parser skeleton and distribute that work
|
||||
under terms of your choice, so long as that work isn't itself a
|
||||
parser generator using the skeleton or a modified version thereof
|
||||
as a parser skeleton. Alternatively, if you modify or redistribute
|
||||
the parser skeleton itself, you may (at your option) remove this
|
||||
special exception, which will cause the skeleton and the resulting
|
||||
Bison output files to be licensed under the GNU General Public
|
||||
License without this special exception.
|
||||
|
||||
This special exception was added by the Free Software Foundation in
|
||||
version 2.2 of Bison. */
|
||||
|
||||
/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
|
||||
especially those whose name start with YY_ or yy_. They are
|
||||
private implementation details that can be changed or removed. */
|
||||
|
||||
#ifndef YY_YY_LANG_TAB_H_INCLUDED
|
||||
# define YY_YY_LANG_TAB_H_INCLUDED
|
||||
/* Debug traces. */
|
||||
#ifndef YYDEBUG
|
||||
# define YYDEBUG 1
|
||||
#endif
|
||||
#if YYDEBUG
|
||||
extern int yydebug;
|
||||
#endif
|
||||
|
||||
/* Token kinds. */
|
||||
#ifndef YYTOKENTYPE
|
||||
# define YYTOKENTYPE
|
||||
enum yytokentype
|
||||
{
|
||||
YYEMPTY = -2,
|
||||
YYEOF = 0, /* "end of file" */
|
||||
YYerror = 256, /* error */
|
||||
YYUNDEF = 257, /* "invalid token" */
|
||||
SIMPLETYPE = 258, /* SIMPLETYPE */
|
||||
CLASS = 259, /* CLASS */
|
||||
ENTRY = 260, /* ENTRY */
|
||||
IF = 261, /* IF */
|
||||
WHILE = 262, /* WHILE */
|
||||
WRITE = 263, /* WRITE */
|
||||
READ = 264, /* READ */
|
||||
RETURN = 265, /* RETURN */
|
||||
IDENTIFIER = 266, /* IDENTIFIER */
|
||||
CONSTANTEXP = 267, /* CONSTANTEXP */
|
||||
LBRACE = 268, /* LBRACE */
|
||||
RBRACE = 269, /* RBRACE */
|
||||
LPAREN = 270, /* LPAREN */
|
||||
RPAREN = 271, /* RPAREN */
|
||||
LBRACKET = 272, /* LBRACKET */
|
||||
RBRACKET = 273, /* RBRACKET */
|
||||
ASSIGN = 274, /* ASSIGN */
|
||||
EQ = 275, /* EQ */
|
||||
LT = 276, /* LT */
|
||||
LE = 277, /* LE */
|
||||
GT = 278, /* GT */
|
||||
GE = 279, /* GE */
|
||||
SEMICOLON = 280, /* SEMICOLON */
|
||||
DOT = 281, /* DOT */
|
||||
PLUS = 282, /* PLUS */
|
||||
MINUS = 283, /* MINUS */
|
||||
MULT = 284, /* MULT */
|
||||
DIV = 285, /* DIV */
|
||||
COMMA = 286 /* COMMA */
|
||||
};
|
||||
typedef enum yytokentype yytoken_kind_t;
|
||||
#endif
|
||||
|
||||
/* Value type. */
|
||||
typedef struct {
|
||||
int intval;
|
||||
char *strval;
|
||||
} YYSTYPE;
|
||||
|
||||
|
||||
extern YYSTYPE yylval;
|
||||
|
||||
|
||||
int yyparse (void);
|
||||
|
||||
|
||||
#endif /* !YY_YY_LANG_TAB_H_INCLUDED */
|
||||
@@ -0,0 +1,153 @@
|
||||
%{
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void yyerror(const char *s);
|
||||
|
||||
typedef struct ASTNode {
|
||||
char *type;
|
||||
char *value;
|
||||
struct ASTNode *left;
|
||||
struct ASTNode *right;
|
||||
} ASTNode;
|
||||
|
||||
|
||||
|
||||
ASTNode *createNode(const char *type, const char *value, ASTNode *left, ASTNode *right);
|
||||
%}
|
||||
|
||||
%token SIMPLETYPE CLASS ENTRY IF WHILE WRITE READ RETURN
|
||||
%token IDENTIFIER
|
||||
%token CONSTANTEXP
|
||||
%token LBRACE RBRACE LPAREN RPAREN LBRACKET RBRACKET ASSIGN EQ LT LE GT GE SEMICOLON DOT PLUS MINUS MULT DIV
|
||||
%token COMMA
|
||||
|
||||
%start program
|
||||
|
||||
%left PLUS MINUS
|
||||
%left MULT DIV
|
||||
%left LT LE GT GE EQ
|
||||
|
||||
%%
|
||||
|
||||
program:
|
||||
CLASS IDENTIFIER LBRACE entry_declaration class_body RBRACE
|
||||
;
|
||||
|
||||
|
||||
class_body:
|
||||
%empty
|
||||
| atrib_declaration class_body
|
||||
| method_declaration class_body
|
||||
;
|
||||
atrib_declaration:
|
||||
SIMPLETYPE IDENTIFIER SEMICOLON
|
||||
| SIMPLETYPE IDENTIFIER ASSIGN CONSTANTEXP SEMICOLON
|
||||
;
|
||||
|
||||
method_declaration:
|
||||
SIMPLETYPE IDENTIFIER LPAREN method_params RPAREN LBRACE method_body RBRACE
|
||||
;
|
||||
|
||||
method_params:
|
||||
%empty
|
||||
| SIMPLETYPE IDENTIFIER
|
||||
| SIMPLETYPE IDENTIFIER COMMA method_params
|
||||
;
|
||||
|
||||
method_body:
|
||||
%empty
|
||||
| statement_list
|
||||
;
|
||||
|
||||
statement_list:
|
||||
%empty
|
||||
| statement statement_list
|
||||
;
|
||||
|
||||
statement:
|
||||
atrib_statement
|
||||
| if_statement
|
||||
| while_statement
|
||||
| write_statement
|
||||
| read_statement
|
||||
| return_statement
|
||||
;
|
||||
|
||||
atrib_statement:
|
||||
IDENTIFIER ASSIGN expression SEMICOLON
|
||||
;
|
||||
|
||||
if_statement:
|
||||
IF LPAREN expression RPAREN LBRACE statement_list RBRACE
|
||||
;
|
||||
|
||||
while_statement:
|
||||
WHILE LPAREN expression RPAREN LBRACE statement_list RBRACE
|
||||
;
|
||||
|
||||
write_statement:
|
||||
WRITE LPAREN expression RPAREN SEMICOLON
|
||||
;
|
||||
|
||||
read_statement:
|
||||
READ LPAREN IDENTIFIER RPAREN SEMICOLON
|
||||
;
|
||||
|
||||
return_statement:
|
||||
RETURN expression SEMICOLON
|
||||
;
|
||||
|
||||
expression:
|
||||
CONSTANTEXP
|
||||
| expression PLUS expression
|
||||
| expression MINUS expression
|
||||
| expression MULT expression
|
||||
| expression DIV expression
|
||||
| expression EQ expression
|
||||
| expression LT expression
|
||||
| expression LE expression
|
||||
| expression GT expression
|
||||
| expression GE expression
|
||||
| LPAREN expression RPAREN
|
||||
| IDENTIFIER
|
||||
| IDENTIFIER LBRACKET expression RBRACKET
|
||||
| LBRACKET array_list RBRACKET
|
||||
| class_method_call
|
||||
| class_expression
|
||||
;
|
||||
|
||||
class_expression:
|
||||
IDENTIFIER
|
||||
| IDENTIFIER DOT class_expression
|
||||
;
|
||||
|
||||
class_method_call:
|
||||
class_expression LPAREN array_list RPAREN
|
||||
|
||||
array_list:
|
||||
%empty
|
||||
| expression
|
||||
| expression COMMA array_list
|
||||
;
|
||||
|
||||
entry_declaration:
|
||||
SIMPLETYPE ENTRY IDENTIFIER LPAREN RPAREN LBRACE statement_list RBRACE
|
||||
;
|
||||
|
||||
|
||||
%%
|
||||
|
||||
void yyerror(const char *s) {
|
||||
fprintf(stderr, "Error: %s\n", s);
|
||||
}
|
||||
|
||||
ASTNode *createNode(const char *type, const char *value, ASTNode *left, ASTNode *right) {
|
||||
ASTNode *node = (ASTNode *)malloc(sizeof(ASTNode));
|
||||
node->type = strdup(type);
|
||||
node->value = value ? strdup(value) : NULL;
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
return node;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#test the symbol table
|
||||
from SymbolTable import SymbolTable
|
||||
|
||||
def main():
|
||||
symbol_table = SymbolTable()
|
||||
symbol_table.addIdentifier("a", 0)
|
||||
symbol_table.addIdentifier("ba", 1)
|
||||
symbol_table.addIdentifier("ab", 2)
|
||||
print(symbol_table.getIdentifier("a"))
|
||||
print(symbol_table.getIdentifier("ba"))
|
||||
print(symbol_table.getIdentifier("ab"))
|
||||
print(symbol_table.getPositionIdentifier("a"))
|
||||
print(symbol_table.getPositionIdentifier("ba"))
|
||||
print(symbol_table.getPositionIdentifier("ab"))
|
||||
print(symbol_table.getIdentifier("d"))
|
||||
print(symbol_table.getPositionIdentifier("d"))
|
||||
symbol_table.addIdentifier("d", 4)
|
||||
print(symbol_table.getIdentifier("d"))
|
||||
print(symbol_table.getPositionIdentifier("d"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,16 @@
|
||||
CLASS
|
||||
IDENTIFIER Main
|
||||
LBRACE
|
||||
SIMPLETYPE
|
||||
ENTRY
|
||||
IDENTIFIER Main
|
||||
LPAREN
|
||||
RPAREN
|
||||
LBRACE
|
||||
WRITE
|
||||
LPAREN
|
||||
CONSTANTEXP 1
|
||||
RPAREN
|
||||
SEMICOLON
|
||||
RBRACE
|
||||
RBRACE
|
||||
@@ -0,0 +1,5 @@
|
||||
class Main{
|
||||
void entry Main(){
|
||||
write(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class Main{
|
||||
|
||||
Problem1err(number, power){
|
||||
return 01 ** power
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
class Main{
|
||||
void entry Main(){
|
||||
Problem2([3,5,7,2])
|
||||
}
|
||||
|
||||
int Problem2(int[] numbers){
|
||||
var prod = 0;
|
||||
int index = 0;
|
||||
while(index < numbers.lenght){
|
||||
int number = numbers[index];
|
||||
prod *= number;
|
||||
}
|
||||
index = index + 1;
|
||||
return prod;
|
||||
}
|
||||
|
||||
void Problem3(int[] numbers1, int[] numbers2){
|
||||
while(index < numbers.lenght){
|
||||
numbers1[index] = numbers1[index] + numbers2[index];
|
||||
index = index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
class Main{
|
||||
void entry Main(){
|
||||
int[] x = [3,5,7,2]
|
||||
Problem3(x,[8,4,2,8])
|
||||
|
||||
}
|
||||
void Problem3(int[] numbers1, int[] numbers2){
|
||||
while(index < numbers.lenght){
|
||||
numbers1[index] = numbers1[index] + numbers2[index];
|
||||
index = index + 1;
|
||||
}
|
||||
}
|
||||
Problem1err(number, power){
|
||||
return number ** power
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
class
|
||||
@@ -0,0 +1,32 @@
|
||||
+
|
||||
-
|
||||
*
|
||||
/
|
||||
[
|
||||
|
||||
{
|
||||
}
|
||||
(
|
||||
)
|
||||
;
|
||||
]
|
||||
=
|
||||
<
|
||||
>
|
||||
<=
|
||||
>=
|
||||
==
|
||||
;
|
||||
,
|
||||
.
|
||||
int
|
||||
char
|
||||
class
|
||||
if
|
||||
while
|
||||
read
|
||||
write
|
||||
entry
|
||||
return
|
||||
null
|
||||
void
|
||||
@@ -0,0 +1,89 @@
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
.gradle/
|
||||
**/build/
|
||||
**/out/
|
||||
**/buildSrc/build/
|
||||
**/buildSrc/.gradle/
|
||||
**/local.properties
|
||||
|
||||
# Android Studio files
|
||||
.idea/
|
||||
*.iml
|
||||
*.hprof
|
||||
*.orig
|
||||
*.log
|
||||
*.lock
|
||||
*.pyc
|
||||
*.swp
|
||||
*.swo
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.db
|
||||
*.sqlite
|
||||
*.apk
|
||||
*.ap_
|
||||
*.dex
|
||||
*.class
|
||||
*.so
|
||||
*.dll
|
||||
*.pdb
|
||||
*.o
|
||||
*.a
|
||||
*.lib
|
||||
*.nib
|
||||
*.plist
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
*.xcuserdatad
|
||||
*.xcworkspace
|
||||
*.xcuserdata
|
||||
*.xcodeproj
|
||||
*.xcodeproj/project.xcworkspace
|
||||
*.xcodeproj/xcuserdata
|
||||
*.xcodeproj/xcuserdata/*.xcuserdatad
|
||||
*.xcodeproj/xcuserdata/*.xcuserdatad/xcschemes
|
||||
|
||||
# built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Mac files
|
||||
.DS_Store
|
||||
|
||||
# files for the dex VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# generated files
|
||||
bin/
|
||||
gen/
|
||||
|
||||
# Ignore gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
proguard-project.txt
|
||||
|
||||
# Eclipse files
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# Android Studio/IDEA
|
||||
*.iml
|
||||
.idea
|
||||
.kotlin/
|
||||
node_modules/
|
||||
@@ -0,0 +1,15 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,59 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.ma_ui_native"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.ma_ui_native"
|
||||
minSdk = 34
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.ui)
|
||||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.example.ma_ui_native
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.example.ma_ui_native", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.MAUINative"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.MAUINative">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SecondActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.MAUINative">
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.example.ma_ui_native
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
||||
data class Drug(
|
||||
var Id: Int = -1,
|
||||
var Name: String = "",
|
||||
var Category: String = "",
|
||||
var NumberOfUnits: Int = 0,
|
||||
var RetailPrice: Double = 0.0,
|
||||
var Manufacturer: String = ""
|
||||
){
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.example.ma_ui_native;
|
||||
|
||||
public class DrugViewModel {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
package com.example.ma_ui_native
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Edit
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.ma_ui_native.ui.theme.MAUINativeTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
MAUINativeTheme {
|
||||
val openDialog = remember { mutableStateOf(false) }
|
||||
val dialogId = remember { mutableStateOf(-1) }
|
||||
if(openDialog.value){
|
||||
DeleteDialog(
|
||||
dialogId.value,
|
||||
onConfirm = {
|
||||
Service.DeleteDrug(dialogId.value)
|
||||
openDialog.value = false
|
||||
},
|
||||
onDismiss = {
|
||||
openDialog.value = false
|
||||
}
|
||||
)
|
||||
}
|
||||
DrugList({
|
||||
openDialog.value = true
|
||||
dialogId.value = it
|
||||
})
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(48.dp)
|
||||
.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Bottom,
|
||||
horizontalAlignment = Alignment.End,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
FAB(onClick = {
|
||||
context.startActivity(Intent(context, SecondActivity::class.java))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun FAB(onClick: () -> Unit) {
|
||||
FloatingActionButton(
|
||||
onClick = { onClick() },
|
||||
shape = CircleShape,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
Icon(Icons.Outlined.Edit, "Floating action button.")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DrugList(onDelete: (Int) -> Unit){
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(top = 75.5.dp, bottom = 23.5.dp, start = 26.dp, end = 26.dp)
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy((10.dp))
|
||||
){
|
||||
items(
|
||||
Service.GetDrugs(),
|
||||
key = { it.value.Id},
|
||||
){
|
||||
DrugDisplay(it.value, onDelete)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DrugDisplay(drug: Drug, onDelete: (Int) -> Unit){
|
||||
Box {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(MaterialTheme.colorScheme.surface, RoundedCornerShape((12.dp)))
|
||||
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(12.dp))
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 7.dp)
|
||||
.fillMaxWidth()
|
||||
.height(44.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column {
|
||||
Text(drug.Name, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyLarge)
|
||||
Text(drug.Manufacturer, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Button(
|
||||
onClick = { GoToUpdate(drug.Id, context) },
|
||||
colors = ButtonColors(
|
||||
MaterialTheme.colorScheme.surface,
|
||||
MaterialTheme.colorScheme.primary,
|
||||
MaterialTheme.colorScheme.surface,
|
||||
MaterialTheme.colorScheme.primary
|
||||
),
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline)
|
||||
) {
|
||||
Text("Edit")
|
||||
}
|
||||
Button(
|
||||
onClick = { onDelete(drug.Id) },
|
||||
colors = ButtonColors(
|
||||
MaterialTheme.colorScheme.primary,
|
||||
MaterialTheme.colorScheme.onPrimary,
|
||||
MaterialTheme.colorScheme.primary,
|
||||
MaterialTheme.colorScheme.onPrimary
|
||||
),
|
||||
) {
|
||||
Text("Delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 7.dp)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 7.dp)
|
||||
.shadow(2.dp, shape = RoundedCornerShape(12.dp))
|
||||
.fillMaxWidth()
|
||||
.height(44.dp)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.background(MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp))
|
||||
.fillMaxWidth(0.38f),
|
||||
) {
|
||||
Text("Category:", color = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.titleSmall)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(0.62f),
|
||||
) {
|
||||
Text(drug.Category, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 7.dp)
|
||||
.shadow(2.dp, shape = RoundedCornerShape(12.dp))
|
||||
.fillMaxWidth()
|
||||
.height(44.dp)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.background(MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp))
|
||||
.fillMaxWidth(0.38f),
|
||||
) {
|
||||
Text("# of Units:", color = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.titleSmall)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(0.62f),
|
||||
) {
|
||||
Text(drug.NumberOfUnits.toString(), color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 40.dp)
|
||||
.shadow(2.dp, shape = RoundedCornerShape(12.dp))
|
||||
.fillMaxWidth()
|
||||
.height(44.dp)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.background(MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp))
|
||||
.fillMaxWidth(0.38f),
|
||||
) {
|
||||
Text("Price:", color = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.titleSmall)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(0.62f),
|
||||
) {
|
||||
Text("$%.2f".format(drug.RetailPrice), color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun GoToUpdate(id: Int, context: Context){
|
||||
Log.d("Update", "Update for %d was clicked".format(id))
|
||||
val bundle = Bundle()
|
||||
bundle.putInt("id", id)
|
||||
val intend = Intent(context, SecondActivity::class.java)
|
||||
intend.putExtras(bundle)
|
||||
context.startActivity(intend)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeleteDialog(id: Int, onConfirm: () -> Unit, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
onDismiss()
|
||||
},
|
||||
title = {
|
||||
Text("Delete Drug")
|
||||
},
|
||||
text = {
|
||||
Text("Are you sure you want to delete this drug? This action can not be reversed")
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
onConfirm()
|
||||
}
|
||||
) {
|
||||
Text("Delete")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
}
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package com.example.ma_ui_native
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.ma_ui_native.ui.theme.MAUINativeTheme
|
||||
|
||||
class SecondActivity : ComponentActivity() {
|
||||
private var drug = Drug()
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if(intent.hasExtra("id")) {
|
||||
val id = intent.getIntExtra("id", -1)
|
||||
if(id != -1) {
|
||||
drug = Service.GetDrug(id)
|
||||
}
|
||||
}
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
MAUINativeTheme {
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(top = 63.dp, start = 62.dp, end = 62.dp, bottom = 73.dp)
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
)
|
||||
{
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 25.dp, start = 61.dp, end = 61.dp)
|
||||
.fillMaxWidth()
|
||||
.height(49.dp)
|
||||
.border(
|
||||
1.dp,
|
||||
color = MaterialTheme.colorScheme.outlineVariant,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(if(drug.Id == -1) "Add Drug" else "Update Drug", color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 30.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
val drugName = remember { mutableStateOf(drug.Name) }
|
||||
TextField(
|
||||
value = drugName.value,
|
||||
onValueChange = {
|
||||
drugName.value = it
|
||||
drug.Name = it
|
||||
},
|
||||
label = {Text("Name")},
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
drugName.value = ""
|
||||
drug.Name = ""
|
||||
}
|
||||
)
|
||||
{
|
||||
Icon(
|
||||
Icons.Filled.Clear,
|
||||
contentDescription = "Clear",
|
||||
)
|
||||
} },
|
||||
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
|
||||
)
|
||||
|
||||
val drugManufacturer = remember { mutableStateOf(drug.Manufacturer) }
|
||||
TextField(
|
||||
value = drugManufacturer.value,
|
||||
onValueChange = {
|
||||
drugManufacturer.value = it
|
||||
drug.Manufacturer = it
|
||||
},
|
||||
label = {Text("Manufacturer")},
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
drugManufacturer.value = ""
|
||||
drug.Manufacturer = ""
|
||||
}
|
||||
)
|
||||
{
|
||||
Icon(
|
||||
Icons.Filled.Clear,
|
||||
contentDescription = "Clear",
|
||||
)
|
||||
} },
|
||||
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
|
||||
)
|
||||
|
||||
val drugCategory = remember { mutableStateOf(drug.Category) }
|
||||
TextField(
|
||||
value = drugCategory.value,
|
||||
onValueChange = {
|
||||
drugCategory.value = it
|
||||
drug.Category = it
|
||||
},
|
||||
label = {Text("Category")},
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
drugCategory.value = ""
|
||||
drug.Category = ""
|
||||
}
|
||||
)
|
||||
{
|
||||
Icon(
|
||||
Icons.Filled.Clear,
|
||||
contentDescription = "Clear",
|
||||
)
|
||||
} },
|
||||
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
|
||||
)
|
||||
|
||||
val drugNumberOfUnits = remember { mutableStateOf(drug.NumberOfUnits.toString()) }
|
||||
if (drugNumberOfUnits.value == "0") {
|
||||
drugNumberOfUnits.value = ""
|
||||
}
|
||||
TextField(
|
||||
value = drugNumberOfUnits.value,
|
||||
onValueChange = {
|
||||
drugNumberOfUnits.value = it
|
||||
drug.NumberOfUnits = it.toIntOrNull() ?: 0
|
||||
},
|
||||
label = {Text("Number of Units")},
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
drugNumberOfUnits.value = ""
|
||||
drug.NumberOfUnits = 0
|
||||
}
|
||||
)
|
||||
{
|
||||
Icon(
|
||||
Icons.Filled.Clear,
|
||||
contentDescription = "Clear",
|
||||
)
|
||||
} },
|
||||
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
|
||||
)
|
||||
|
||||
val drugRetailPrice = remember { mutableStateOf(drug.RetailPrice.toString()) }
|
||||
if (drugRetailPrice.value == "0.0") {
|
||||
drugRetailPrice.value = ""
|
||||
}
|
||||
TextField(
|
||||
value = drugRetailPrice.value,
|
||||
onValueChange = {
|
||||
drugRetailPrice.value = it
|
||||
drug.RetailPrice = (it.toDoubleOrNull() ?: 0.0)
|
||||
},
|
||||
label = {Text("Retail Price")},
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
drugRetailPrice.value = ""
|
||||
drug.RetailPrice = 0.0
|
||||
}
|
||||
)
|
||||
{
|
||||
Icon(
|
||||
Icons.Filled.Clear,
|
||||
contentDescription = "Clear",
|
||||
)
|
||||
} },
|
||||
supportingText = { Text(text = "*required", color = MaterialTheme.colorScheme.error) },
|
||||
prefix = { Text("$") },
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = { finish() },
|
||||
colors = ButtonColors(
|
||||
MaterialTheme.colorScheme.surface,
|
||||
MaterialTheme.colorScheme.primary,
|
||||
MaterialTheme.colorScheme.surface,
|
||||
MaterialTheme.colorScheme.primary
|
||||
),
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline)
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
if(drug.Id == -1) {
|
||||
Service.AddDrug(drug)
|
||||
} else {
|
||||
Service.UpdateDrug(drug)
|
||||
}
|
||||
finish()
|
||||
},
|
||||
colors = ButtonColors(
|
||||
MaterialTheme.colorScheme.primary,
|
||||
MaterialTheme.colorScheme.onPrimary,
|
||||
MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
enabled = drug.Name.isNotEmpty() && drug.Manufacturer.isNotEmpty() && drug.Category.isNotEmpty() && drug.NumberOfUnits > 0 && drug.RetailPrice > 0.0
|
||||
) {
|
||||
Text(if(drug.Id == -1) "Add" else "Update")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.example.ma_ui_native
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
|
||||
object Service {
|
||||
private var nextId = 11
|
||||
private val DrugRepo = mutableStateListOf<MutableState<Drug>>(
|
||||
mutableStateOf( Drug(1, "Paracetamol", "Analgesic", 100, 2.5, "GSK")),
|
||||
mutableStateOf(Drug(2, "Amoxicillin", "Antibiotic", 50, 3.5, "Pfizer")),
|
||||
mutableStateOf(Drug(3, "Omeprazole", "Antacid", 75, 1.5, "AstraZeneca")),
|
||||
mutableStateOf(Drug(4, "Atorvastatin", "Anticholesterol", 25, 4.5, "Pfizer")),
|
||||
mutableStateOf(Drug(5, "Metformin", "Antidiabetic", 30, 2.0, "Merck")),
|
||||
mutableStateOf(Drug(6, "Amlodipine", "Antihypertensive", 40, 3.0, "Novartis")),
|
||||
mutableStateOf(Drug(7, "Losartan", "Antihypertensive", 35, 3.5, "Merck")),
|
||||
mutableStateOf(Drug(8, "Simvastatin", "Anticholesterol", 20, 4.0, "AstraZeneca")),
|
||||
mutableStateOf(Drug(9, "Salbutamol", "Bronchodilator", 45, 2.5, "GSK")),
|
||||
mutableStateOf(Drug(10, "Levothyroxine", "Hormone", 60, 1.5, "Novartis"))
|
||||
)
|
||||
public fun GetDrugs() : SnapshotStateList<MutableState<Drug>> {
|
||||
return DrugRepo;
|
||||
}
|
||||
|
||||
public fun GetDrug(id: Int) : Drug {
|
||||
return DrugRepo.find { it.value.Id == id }?.value ?: Drug()
|
||||
}
|
||||
|
||||
public fun AddDrug(drug: Drug) {
|
||||
drug.Id = nextId++
|
||||
DrugRepo.add(mutableStateOf( drug))
|
||||
}
|
||||
|
||||
public fun UpdateDrug(drug: Drug) {
|
||||
val index = DrugRepo.indexOfFirst { it.value.Id == drug.Id }
|
||||
if (index != -1) {
|
||||
DrugRepo[index] = mutableStateOf( drug)
|
||||
}
|
||||
}
|
||||
|
||||
public fun DeleteDrug(id: Int) {
|
||||
val index = DrugRepo.indexOfFirst { it.value.Id == id }
|
||||
if (index != -1) {
|
||||
DrugRepo.removeAt(index)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.example.ma_ui_native.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
var Primary = Color(0xFF675496)
|
||||
var OnPrimary = Color(0xFFFFFFFF)
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.example.ma_ui_native.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun MAUINativeTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.example.ma_ui_native.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="m336,680 l144,-144 144,144 56,-56 -144,-144 144,-144 -56,-56 -144,144 -144,-144 -56,56 144,144 -144,144 56,56ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93ZM480,480Z"
|
||||
android:fillColor="#e8eaed"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">MA-UI-Native</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.MAUINative" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.example.ma_ui_native
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. For more details, visit
|
||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
@@ -0,0 +1,32 @@
|
||||
[versions]
|
||||
agp = "8.7.1"
|
||||
kotlin = "2.0.0"
|
||||
coreKtx = "1.10.1"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.1.5"
|
||||
espressoCore = "3.5.1"
|
||||
lifecycleRuntimeKtx = "2.6.1"
|
||||
activityCompose = "1.8.0"
|
||||
composeBom = "2024.04.01"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#Sun Oct 27 15:55:16 EET 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,23 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google {
|
||||
content {
|
||||
includeGroupByRegex("com\\.android.*")
|
||||
includeGroupByRegex("com\\.google.*")
|
||||
includeGroupByRegex("androidx.*")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "MA-UI-Native"
|
||||
include(":app")
|
||||
@@ -0,0 +1,39 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
expo-env.d.ts
|
||||
|
||||
# Native
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
app-example
|
||||
android/
|
||||
@@ -0,0 +1,50 @@
|
||||
# Welcome to your Expo app 👋
|
||||
|
||||
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
||||
|
||||
## Get started
|
||||
|
||||
1. Install dependencies
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Start the app
|
||||
|
||||
```bash
|
||||
npx expo start
|
||||
```
|
||||
|
||||
In the output, you'll find options to open the app in a
|
||||
|
||||
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
||||
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
||||
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
||||
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
||||
|
||||
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
||||
|
||||
## Get a fresh project
|
||||
|
||||
When you're ready, run:
|
||||
|
||||
```bash
|
||||
npm run reset-project
|
||||
```
|
||||
|
||||
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
|
||||
|
||||
## Learn more
|
||||
|
||||
To learn more about developing your project with Expo, look at the following resources:
|
||||
|
||||
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
||||
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
|
||||
|
||||
## Join the community
|
||||
|
||||
Join our community of developers creating universal apps.
|
||||
|
||||
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
||||
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "MA-NonNative",
|
||||
"slug": "MA-NonNative",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "myapp",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"newArchEnabled": true,
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"package": "com.danielcujba54.MANonNative"
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
"output": "static",
|
||||
"favicon": "./assets/images/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
{
|
||||
"image": "./assets/images/splash-icon.png",
|
||||
"imageWidth": 200,
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
],
|
||||
["expo-sqlite",
|
||||
{
|
||||
"enableFTS": true,
|
||||
"useSQLCipher": true,
|
||||
"android": {
|
||||
"enableFTS": false,
|
||||
"useSQLCipher": false
|
||||
},
|
||||
"ios": {
|
||||
"customBuildFlags": ["-DSQLITE_ENABLE_DBSTAT_VTAB=1 -DSQLITE_ENABLE_SNAPSHOT=1"]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"android": {
|
||||
"usesCleartextTraffic": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
},
|
||||
"extra": {
|
||||
"router": {
|
||||
"origin": false
|
||||
},
|
||||
"eas": {
|
||||
"projectId": "295efd7e-de78-4e54-b327-8eec9a3f9bf0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { store } from "@/redux/store";
|
||||
import { Stack } from "expo-router";
|
||||
import { Provider } from "react-redux";
|
||||
import { useMaterial3Theme } from "@pchmn/expo-material3-theme";
|
||||
import { MD3DarkTheme, MD3LightTheme, PaperProvider } from "react-native-paper";
|
||||
import { StatusBar, useColorScheme, Text } from "react-native";
|
||||
import { DataLoader } from "@/components/DataLoader";
|
||||
|
||||
|
||||
export default function RootLayout() {
|
||||
const colorScheme = useColorScheme();
|
||||
const {theme} = useMaterial3Theme();
|
||||
const paperTheme =
|
||||
colorScheme === 'dark'
|
||||
? { ...MD3DarkTheme, colors: theme.dark }
|
||||
: { ...MD3LightTheme, colors: theme.light };
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<DataLoader />
|
||||
<PaperProvider theme={paperTheme}>
|
||||
<StatusBar
|
||||
hidden={true}
|
||||
/>
|
||||
<Stack>
|
||||
<Stack.Screen name="index" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="add" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="update/[id]" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
</PaperProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import DrugList from "@/components/DrugList";
|
||||
import Form from "@/components/Form";
|
||||
import { Link } from "expo-router";
|
||||
import { Dimensions, Text, View } from "react-native";
|
||||
import { useTheme } from "react-native-paper";
|
||||
|
||||
export default function Add() {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<View style={{width: "100%", backgroundColor:theme.colors.surface, height: Dimensions.get('window').height}}>
|
||||
<Form />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import DrugList from "@/components/DrugList";
|
||||
import { Dimensions, Text, View } from "react-native";
|
||||
import { useTheme } from "react-native-paper";
|
||||
|
||||
|
||||
export default function Index() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<View style={{width: "100%", backgroundColor:theme.colors.surface, height: Dimensions.get('window').height}}>
|
||||
<DrugList />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import Form from "@/components/Form";
|
||||
import { Link, useLocalSearchParams } from "expo-router";
|
||||
import { Dimensions, Text, View } from "react-native";
|
||||
import { useTheme } from "react-native-paper";
|
||||
|
||||
export default function Update() {
|
||||
const { id } = useLocalSearchParams() as { id: string };
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<View style={{width: "100%", backgroundColor:theme.colors.surface, height: Dimensions.get('window').height}}>
|
||||
<Form drugId={parseInt(id)} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,38 @@
|
||||
import { RootState } from "@/redux/store";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Snackbar } from "react-native-paper";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export const ConnectionNotification = () => {
|
||||
const dispatch = useDispatch();
|
||||
const status = useSelector((state: RootState) => state.status);
|
||||
const [visible, setVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!status.isConnected || !status.isOnline) {
|
||||
setVisible(true);
|
||||
}
|
||||
else{
|
||||
setVisible(false);
|
||||
}
|
||||
}, [status.isConnected, status.isOnline]);
|
||||
const onDismiss = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
return (
|
||||
<Snackbar
|
||||
wrapperStyle={{ top: 50, left: "50%", position: 'absolute', transform: [{ translateX: "-42%" }], zIndex: 9999}}
|
||||
visible={visible}
|
||||
onDismiss={onDismiss}
|
||||
style={{ zIndex: 9999, }}
|
||||
action={{
|
||||
label: 'Dismiss',
|
||||
onPress: () => {
|
||||
setVisible(false);
|
||||
},
|
||||
}}
|
||||
duration={60000}
|
||||
>
|
||||
No internet connection
|
||||
</Snackbar>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { initDB } from "@/database/localdb";
|
||||
import { useEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
|
||||
export const DataLoader = () => {
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
dispatch({ type: 'drug/initialize' });
|
||||
dispatch({ type: 'drug/fetch' });
|
||||
};
|
||||
fetchData();
|
||||
}, [dispatch]);
|
||||
return (<View></View>);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import React from "react";
|
||||
import { Button, Dialog, MD3Theme, Portal, useTheme, Text } from "react-native-paper";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
export default function DeletePopUp(props: {drugId: number, onCancel: () => void, onDelete: () => void}) {
|
||||
const theme = useTheme();
|
||||
const style = styles(theme);
|
||||
const navigation = useNavigation<any>(); //TODO: types...
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog
|
||||
visible={true}
|
||||
>
|
||||
<Dialog.Title>Delete Drug</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
<Text variant="bodyMedium">Are you sure you want to delete this drug? This action can not be reversed</Text>
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button contentStyle={{paddingHorizontal: 8}} mode="contained" onPress={props.onCancel}>Cancel</Button>
|
||||
<Button contentStyle={{paddingHorizontal: 8}} mode="contained" onPress={props.onDelete}>Delete</Button>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = (theme: MD3Theme) => StyleSheet.create({
|
||||
|
||||
})
|
||||
@@ -0,0 +1,204 @@
|
||||
import { Drug } from "@/redux/features/drug/drug";
|
||||
import { View, Text, StyleSheet } from "react-native";
|
||||
import { Button, Divider, MD3Theme, useTheme } from "react-native-paper";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "@/redux/store";
|
||||
|
||||
export default function DrugDisplay(props: {drug: Drug, onUpdate: (drugId: number) => void, onDelete: (drugId: number) => void}) {
|
||||
const theme = useTheme();
|
||||
const style = styles(theme);
|
||||
const status = useSelector((state: RootState) => state.status);
|
||||
|
||||
return (
|
||||
<View className="box" style={style.outerbox}>
|
||||
<View className="box" style={style.boxBackground} >
|
||||
|
||||
</View>
|
||||
<View className="column" style={style.boxForeground}>
|
||||
<View style={style.rowHeadlineMargin}>
|
||||
<View className="row" style={style.rowHeadline}>
|
||||
<View className="column" style={style.column}>
|
||||
<Text style={style.nameText}>{props.drug.name}</Text>
|
||||
<Text style={style.manufacturerText}>{props.drug.manufacturer}</Text>
|
||||
</View>
|
||||
<View className="row" style={style.rowCTA}>
|
||||
<Button disabled={!status.isConnected || !status.isOnline} contentStyle={{paddingHorizontal: 8}} textColor={theme.colors.primary} buttonColor={theme.colors.surface} style={style.editButton} onPress={() => props.onUpdate(props.drug.id)}>Edit</Button>
|
||||
<Button contentStyle={{paddingHorizontal: 8}} textColor={theme.colors.onPrimary} buttonColor={theme.colors.primary} onPress={() => props.onDelete(props.drug.id)} >Delete</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<Divider style={style.horizontalDivider} />
|
||||
<View style={style.chipMargin}>
|
||||
<View className="row" style={style.chip}>
|
||||
<View className="box" style={style.labelChip}>
|
||||
<Text style={style.label}>Category:</Text>
|
||||
</View>
|
||||
<View className="box" style={style.valueChip}>
|
||||
<Text style={style.value}>{props.drug.category}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={style.chipMargin}>
|
||||
<View className="row" style={style.chip}>
|
||||
<View className="box" style={style.labelChip}>
|
||||
<Text style={style.label}># of Units:</Text>
|
||||
</View>
|
||||
<View className="box" style={style.valueChip}>
|
||||
<Text style={style.value}>{props.drug.numberOfUnits.toFixed(0)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{...style.chipMargin, ...style.lastChipMargin}}>
|
||||
<View className="row" style={style.chip}>
|
||||
<View className="box" style={style.labelChip}>
|
||||
<Text style={style.label}>Price:</Text>
|
||||
</View>
|
||||
<View className="box" style={style.valueChip}>
|
||||
<Text style={style.value}>${props.drug.price.toFixed(2)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = (theme: MD3Theme) => StyleSheet.create({
|
||||
boxBackground: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: theme.colors.surface,
|
||||
borderRadius: 12,
|
||||
borderColor: theme.colors.outlineVariant,
|
||||
borderWidth: 1,
|
||||
zIndex: 10,
|
||||
},
|
||||
boxForeground: {
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 20,
|
||||
|
||||
},
|
||||
outerbox: {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
marginBottom: 8
|
||||
},
|
||||
rowHeadlineMargin:{
|
||||
marginTop: 16,
|
||||
marginRight: 16,
|
||||
marginBottom: 7,
|
||||
marginLeft: 16,
|
||||
},
|
||||
rowHeadline: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
height: 44,
|
||||
|
||||
},
|
||||
column:{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
rowCTA: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 8,
|
||||
},
|
||||
nameText:{
|
||||
color: theme.colors.onSurface,
|
||||
fontFamily: theme.fonts.bodyLarge.fontFamily,
|
||||
fontSize: theme.fonts.bodyLarge.fontSize,
|
||||
lineHeight: theme.fonts.bodyLarge.lineHeight,
|
||||
letterSpacing: theme.fonts.bodyLarge.letterSpacing,
|
||||
fontWeight: theme.fonts.bodyLarge.fontWeight,
|
||||
fontStyle: theme.fonts.bodyLarge.fontStyle
|
||||
},
|
||||
manufacturerText:{
|
||||
color: theme.colors.onSurfaceVariant,
|
||||
fontFamily: theme.fonts.bodyMedium.fontFamily,
|
||||
fontSize: theme.fonts.bodyMedium.fontSize,
|
||||
lineHeight: theme.fonts.bodyMedium.lineHeight,
|
||||
letterSpacing: theme.fonts.bodyMedium.letterSpacing,
|
||||
fontWeight: theme.fonts.bodyMedium.fontWeight,
|
||||
fontStyle: theme.fonts.bodyMedium.fontStyle
|
||||
},
|
||||
editButton:{
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.outline,
|
||||
},
|
||||
horizontalDivider:{
|
||||
marginRight: 16,
|
||||
marginBottom: 16,
|
||||
marginLeft: 7,
|
||||
},
|
||||
|
||||
chipMargin:{
|
||||
marginLeft: 16,
|
||||
marginRight: 16,
|
||||
marginBottom: 7,
|
||||
},
|
||||
|
||||
chip: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
shadowRadius: 12, //TODO: shadow wack
|
||||
shadowOffset: { width: 2, height: 2 },
|
||||
width: "100%" ,
|
||||
height: 44,
|
||||
backgroundColor: theme.colors.surfaceDisabled,
|
||||
borderRadius: 12
|
||||
},
|
||||
lastChipMargin: {
|
||||
marginBottom: 40
|
||||
},
|
||||
labelChip: {
|
||||
height: "100%",
|
||||
width: "38%",
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderTopLeftRadius: 12,
|
||||
borderBottomLeftRadius: 12,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
},
|
||||
|
||||
label: {
|
||||
color: theme.colors.onPrimary,
|
||||
textAlign: "center",
|
||||
fontFamily: theme.fonts.titleSmall.fontFamily,
|
||||
fontSize: theme.fonts.titleSmall.fontSize,
|
||||
lineHeight: theme.fonts.titleSmall.lineHeight,
|
||||
letterSpacing: theme.fonts.titleSmall.letterSpacing,
|
||||
fontWeight: theme.fonts.titleSmall.fontWeight,
|
||||
fontStyle: theme.fonts.titleSmall.fontStyle
|
||||
},
|
||||
valueChip: {
|
||||
height: "100%",
|
||||
width: "62%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
},
|
||||
|
||||
value: {
|
||||
color: theme.colors.onSurfaceVariant,
|
||||
textAlign: "center",
|
||||
fontFamily: theme.fonts.bodyMedium.fontFamily,
|
||||
fontSize: theme.fonts.bodyMedium.fontSize,
|
||||
lineHeight: theme.fonts.bodyMedium.lineHeight,
|
||||
letterSpacing: theme.fonts.bodyMedium.letterSpacing,
|
||||
fontWeight: theme.fonts.bodyMedium.fontWeight,
|
||||
fontStyle: theme.fonts.bodyMedium.fontStyle
|
||||
},
|
||||
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Drug } from "@/redux/features/drug/drug";
|
||||
import { View, StyleSheet, FlatList, Dimensions } from "react-native";
|
||||
import { RootState, AppDispatch } from "@/redux/store";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import React, { useState } from "react";
|
||||
import DrugDisplay from "./DrugDisplay";
|
||||
import { FAB, MD3Theme, useTheme } from "react-native-paper";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import DeletePopUp from "./DeletePopUp";
|
||||
import { drugSlice, removeDrug } from "@/redux/features/drug/drugSlice";
|
||||
import { ConnectionNotification } from "./ConnectionNotification";
|
||||
import { ErrorNotification } from "./ErrorNotification";
|
||||
|
||||
export default function DrugList() {
|
||||
const drugs: Drug[] = useSelector((state: RootState) => state.drug.drugList);
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const theme = useTheme();
|
||||
const style = styles(theme);
|
||||
const navigation = useNavigation<any>(); //TODO: types...
|
||||
|
||||
const [deleteIndex, useDeleteIndex] = useState(-1)
|
||||
|
||||
return (
|
||||
<View style={style.listMargin}>
|
||||
<ConnectionNotification />
|
||||
{/* <ErrorNotification /> */}
|
||||
<FAB
|
||||
color={theme.colors.onPrimary}
|
||||
style={style.fab}
|
||||
icon="pencil-outline"
|
||||
mode="elevated"
|
||||
onPress={() => {
|
||||
navigation.navigate("add");
|
||||
}}
|
||||
/>
|
||||
{deleteIndex != -1 ?
|
||||
<DeletePopUp
|
||||
drugId={deleteIndex}
|
||||
onCancel = {() => {
|
||||
useDeleteIndex(-1)
|
||||
}}
|
||||
onDelete = {() => {
|
||||
dispatch({type: "drug/remove", payload: deleteIndex})
|
||||
useDeleteIndex(-1)
|
||||
}}
|
||||
/> : null}
|
||||
<FlatList style={style.list}
|
||||
data={drugs}
|
||||
renderItem={({item})=><DrugDisplay
|
||||
key={item.id}
|
||||
drug={item}
|
||||
onUpdate={(drugId: number) => {
|
||||
navigation.navigate("update/[id]", {id: item.id});
|
||||
}}
|
||||
onDelete={(drugId: number) => {
|
||||
useDeleteIndex(drugId)
|
||||
}} />}
|
||||
keyExtractor={item => item.id.toString()}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = (theme: MD3Theme) => StyleSheet.create({
|
||||
listMargin:{
|
||||
paddingTop: 75.5,
|
||||
paddingBottom: 23.5,
|
||||
paddingLeft: 26,
|
||||
paddingRight: 26,
|
||||
height: Dimensions.get('window').height,
|
||||
flex: 1
|
||||
},
|
||||
list: {
|
||||
backgroundColor: theme.colors.surface,
|
||||
height: "100%"
|
||||
},
|
||||
fab: {
|
||||
position: 'absolute',
|
||||
margin: 48,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 30,
|
||||
borderRadius: 30,
|
||||
backgroundColor: theme.colors.primary
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { RootState } from "@/redux/store";
|
||||
import { useState, useEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Snackbar } from "react-native-paper";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export const ErrorNotification = () => {
|
||||
const dispatch = useDispatch();
|
||||
const status = useSelector((state: RootState) => state.errorStatus);
|
||||
const [visible, setVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
if (status.status) {
|
||||
setVisible(true);
|
||||
}
|
||||
else{
|
||||
setVisible(false);
|
||||
}
|
||||
}, [status.message]);
|
||||
const onDismiss = () => {
|
||||
dispatch({type: "errorStatus/setErrorStatus", payload: {status: false, message: ""}})
|
||||
};
|
||||
return (
|
||||
<View style={{ zIndex: 9999, flex: 1, justifyContent: 'center', position: 'absolute', width: '100%', bottom: 0, left: "50%", transform: [{ translateX: "-42%" }]}}>
|
||||
<Snackbar
|
||||
visible={visible}
|
||||
onDismiss={onDismiss}
|
||||
style={{ zIndex: 9999}}
|
||||
action={{
|
||||
label: 'Dismiss',
|
||||
onPress: () => {
|
||||
setVisible(false);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{status.message}
|
||||
</Snackbar>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import { Drug } from "@/redux/features/drug/drug";
|
||||
import { AppDispatch, RootState } from "@/redux/store";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Dimensions, Text, View, StyleSheet } from "react-native";
|
||||
import { Button, HelperText, MD3Theme, TextInput, useTheme } from "react-native-paper";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export default function Form(props: {drugId?: number}) {
|
||||
const [drug, setDrug] = useState<Drug>({
|
||||
id: -1,
|
||||
name: "",
|
||||
manufacturer: "",
|
||||
category: "",
|
||||
price: 0,
|
||||
numberOfUnits: 0
|
||||
});
|
||||
const [price , setPrice] = useState("0");
|
||||
const updateDrug = useSelector((state: RootState) => state.drug.drugList.find(drug => drug.id === props.drugId));
|
||||
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const theme = useTheme();
|
||||
const style = styles(theme);
|
||||
const navigation = useNavigation<any>(); //TODO: types...
|
||||
useEffect(() => {
|
||||
if(props.drugId){
|
||||
if(updateDrug){
|
||||
setDrug(updateDrug);
|
||||
setPrice(updateDrug.price.toFixed(2));
|
||||
}
|
||||
}
|
||||
}, [updateDrug]);
|
||||
|
||||
const onSubmit = () => {
|
||||
if(props.drugId){
|
||||
dispatch({type: "drug/update", payload: drug});
|
||||
}
|
||||
else{
|
||||
dispatch({type: "drug/add", payload: drug});
|
||||
}
|
||||
}
|
||||
|
||||
const isDisabled = () => {
|
||||
return drug.name === "" || drug.manufacturer === "" || drug.category === "" || drug.price === 0 || drug.numberOfUnits === 0;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.mainMargin}>
|
||||
<View style={style.column}>
|
||||
<View style={style.chipMargin}>
|
||||
<View style={style.chip}>
|
||||
<Text style={style.chipLabel}>{props.drugId ? "Update Drug" : "Add Drug"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={style.fieldList}>
|
||||
<View style={style.inputWrapper}>
|
||||
<TextInput
|
||||
label="Name"
|
||||
value={drug?.name}
|
||||
onChangeText={(text) => {
|
||||
setDrug({...drug, name: text} as Drug)
|
||||
}}
|
||||
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, name: ""} as Drug)}/>}
|
||||
/>
|
||||
<HelperText type="error" visible={true}>*required</HelperText>
|
||||
</View>
|
||||
<View style={style.inputWrapper}>
|
||||
<TextInput
|
||||
label="Manufacturer"
|
||||
value={drug?.manufacturer}
|
||||
onChangeText={(text) => {
|
||||
setDrug({...drug, manufacturer: text} as Drug)
|
||||
}}
|
||||
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, manufacturer: ""} as Drug)}/>}
|
||||
/>
|
||||
<HelperText type="error" visible={true}>*required</HelperText>
|
||||
</View>
|
||||
<View style={style.inputWrapper}>
|
||||
<TextInput
|
||||
label="Category"
|
||||
value={drug?.category}
|
||||
onChangeText={(text) => {
|
||||
setDrug({...drug, category: text} as Drug)
|
||||
}}
|
||||
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, category: ""} as Drug)}/>}
|
||||
/>
|
||||
<HelperText type="error" visible={true}>*required</HelperText>
|
||||
</View>
|
||||
<View style={style.inputWrapper}>
|
||||
<TextInput
|
||||
label="Number of Units"
|
||||
value={drug?.numberOfUnits.toString()}
|
||||
keyboardType="numeric"
|
||||
onChangeText={(text) => {
|
||||
if(text === ""){
|
||||
setDrug({...drug, numberOfUnits: 0} as Drug)
|
||||
}
|
||||
else{
|
||||
setDrug({...drug, numberOfUnits: parseInt(text)} as Drug)
|
||||
}
|
||||
}}
|
||||
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, numberOfUnits: 0} as Drug)}/>}
|
||||
/>
|
||||
<HelperText type="error" visible={true}>*required</HelperText>
|
||||
</View>
|
||||
<View style={style.inputWrapper}>
|
||||
<TextInput
|
||||
label="Price"
|
||||
value={price}
|
||||
keyboardType="numeric"
|
||||
onChangeText={(text) => {
|
||||
setPrice(text);
|
||||
if(text === "" || isNaN(Number(text)) || text[text.length - 1] === '.'){
|
||||
setDrug({...drug, price: 0} as Drug)
|
||||
}
|
||||
else{
|
||||
setPrice(parseFloat(text).toString());
|
||||
setDrug({...drug, price: parseFloat(text)} as Drug)
|
||||
}
|
||||
}}
|
||||
right={<TextInput.Icon icon="close" onPress={() => {setDrug({...drug, price: 0} as Drug); setPrice("")}}/>}
|
||||
left={<TextInput.Affix text="$ " />}
|
||||
/>
|
||||
<HelperText type="error" visible={true}>*required</HelperText>
|
||||
</View>
|
||||
</View>
|
||||
<View style={style.buttonRow}>
|
||||
<Button mode="outlined" onPress={() => navigation.navigate("index")}>Cancel</Button>
|
||||
<Button mode="contained"
|
||||
textColor={theme.colors.onPrimary}
|
||||
buttonColor={theme.colors.primary}
|
||||
onPress={() => {
|
||||
if(!isDisabled())
|
||||
{
|
||||
onSubmit();
|
||||
navigation.navigate("index")
|
||||
}
|
||||
}}>{props.drugId ? "Update" : "Add"}</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = (theme: MD3Theme) => StyleSheet.create({
|
||||
mainMargin:{
|
||||
paddingTop: 63,
|
||||
paddingBottom: 62,
|
||||
paddingLeft: 62,
|
||||
paddingRight: 73,
|
||||
height: Dimensions.get('window').height,
|
||||
flex: 1
|
||||
},
|
||||
column: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.colors.surface,
|
||||
},
|
||||
chipMargin: {
|
||||
paddingBottom: 25,
|
||||
paddingLeft: 61,
|
||||
paddingRight: 61,
|
||||
},
|
||||
chip: {
|
||||
height: 49,
|
||||
width: "100%",
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.outlineVariant,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
chipLabel: {
|
||||
color: theme.colors.onSurface,
|
||||
textAlign: "center",
|
||||
fontFamily: theme.fonts.labelLarge.fontFamily,
|
||||
fontSize: theme.fonts.labelLarge.fontSize,
|
||||
lineHeight: theme.fonts.labelLarge.lineHeight,
|
||||
letterSpacing: theme.fonts.labelLarge.letterSpacing,
|
||||
fontWeight: theme.fonts.labelLarge.fontWeight,
|
||||
fontStyle: theme.fonts.labelLarge.fontStyle
|
||||
|
||||
},
|
||||
fieldList: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginBottom: 30,
|
||||
width: "100%"
|
||||
},
|
||||
buttonRow: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
gap: 8
|
||||
},
|
||||
inputWrapper: {
|
||||
marginBottom: 16,
|
||||
width: "100%"
|
||||
}
|
||||
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
export const SERVER_IP = "192.168.0.45" as const;
|
||||
export const SERVER_PORT = 3000 as const;
|
||||
export const WS_PORT = 8080 as const;
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Drug } from '@/redux/features/drug/drug';
|
||||
import { openDatabaseAsync, SQLiteDatabase } from 'expo-sqlite'
|
||||
export const getDBConnection = async () => {
|
||||
return await openDatabaseAsync("drug.db");
|
||||
};
|
||||
|
||||
export type TempDrug = {
|
||||
id: number,
|
||||
type: number,
|
||||
drug?: Drug
|
||||
}
|
||||
|
||||
export const createTable = async (db: SQLiteDatabase) => {
|
||||
const exists = await db.getAllAsync(`SELECT name FROM sqlite_master WHERE type='table' AND name='Drug';`);
|
||||
if (exists.length > 0) {
|
||||
return;
|
||||
}
|
||||
const query = `CREATE TABLE IF NOT EXISTS Drug (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
category TEXT,
|
||||
price REAL,
|
||||
numberOfUnits INTEGER,
|
||||
manufacturer TEXT
|
||||
);`;
|
||||
await db.runAsync(query);
|
||||
const tempTable = `CREATE TABLE IF NOT EXISTS TempDrug (
|
||||
id INTEGER,
|
||||
type INTEGER);`;
|
||||
await db.runAsync(tempTable);
|
||||
};
|
||||
|
||||
export const insertDrug = async (db: SQLiteDatabase, drug: Drug) => {
|
||||
const query = `INSERT INTO Drug (${drug.id !== -1 ? "id," : ""} name, category, price, numberOfUnits, manufacturer) VALUES (${drug.id !== -1 ? "?," : ""} ?, ?, ?, ?, ?);`;
|
||||
const values = [drug.name, drug.category, drug.price, drug.numberOfUnits, drug.manufacturer];
|
||||
if (drug.id !== -1) {
|
||||
values.unshift(drug.id);
|
||||
}
|
||||
const response = await db.runAsync(query, values);
|
||||
console.log("Inserted into local db");
|
||||
return response.lastInsertRowId;
|
||||
}
|
||||
|
||||
export const getDrugs = async (db: SQLiteDatabase) => {
|
||||
const query = `SELECT * FROM Drug;`;
|
||||
const response = await db.getAllAsync(query);
|
||||
const drugs: Drug[] = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
drugs.push(response[i] as Drug);
|
||||
}
|
||||
return drugs;
|
||||
}
|
||||
|
||||
export const getDrug = async (db: SQLiteDatabase, id: number) => {
|
||||
const query = `SELECT * FROM Drug WHERE id = ?;`;
|
||||
const values = [id];
|
||||
const response = await db.getFirstAsync(query, values);
|
||||
return response as Drug;
|
||||
}
|
||||
|
||||
export const editDrug = async (db: SQLiteDatabase, drug: Drug) => {
|
||||
const query = `UPDATE Drug SET name = ?, category = ?, price = ?, numberOfUnits = ?, manufacturer = ? WHERE id = ?;`;
|
||||
const values = [drug.name, drug.category, drug.price, drug.numberOfUnits, drug.manufacturer, drug.id];
|
||||
await db.runAsync(query, values);
|
||||
}
|
||||
|
||||
export const deleteDrug = async (db: SQLiteDatabase, id: number) => {
|
||||
const query = `DELETE FROM Drug WHERE id = ?;`;
|
||||
const values = [id];
|
||||
await db.runAsync(query, values);
|
||||
console.log("Deleted from local db");
|
||||
}
|
||||
|
||||
export const rehydrateLocalDB = async (drugs: Drug[]) => {
|
||||
const db = await getDBConnection();
|
||||
await db.runAsync(`DELETE FROM Drug;`);
|
||||
for (let i = 0; i < drugs.length; i++) {
|
||||
await insertDrug(db, drugs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const initDB = async () => {
|
||||
const db = await getDBConnection();
|
||||
await createTable(db);
|
||||
return db;
|
||||
}
|
||||
|
||||
export const insertTempDrug = async (db: SQLiteDatabase, tempDrug: TempDrug) => {
|
||||
console.log("Inserting temp drug");
|
||||
console.log(tempDrug);
|
||||
const query = `INSERT INTO TempDrug (id, type) VALUES (?, ?);`;
|
||||
const values = [tempDrug.id, tempDrug.type];
|
||||
await db.runAsync(query, values);
|
||||
}
|
||||
|
||||
export const getTempDrugs = async (db: SQLiteDatabase) => {
|
||||
const query = `SELECT Drug.*, TempDrug.id, TempDrug.type FROM TempDrug LEFT JOIN Drug ON TempDrug.id = Drug.id;`;
|
||||
const response = await db.getAllAsync(query);
|
||||
const tempDrugs: TempDrug[] = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const responseDrug = response[i] as any;
|
||||
const tempDrug = {
|
||||
id: responseDrug.id,
|
||||
type: responseDrug.type,
|
||||
drug: responseDrug.name ? {
|
||||
id: responseDrug.id,
|
||||
name: responseDrug.name,
|
||||
category: responseDrug.category,
|
||||
price: responseDrug.price,
|
||||
numberOfUnits: responseDrug.numberOfUnits,
|
||||
manufacturer: responseDrug.manufacturer
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
tempDrugs.push(tempDrug);
|
||||
}
|
||||
return tempDrugs;
|
||||
}
|
||||
|
||||
export const deleteTempDrug = async (db: SQLiteDatabase) => {
|
||||
const query = `DELETE FROM TempDrug`;
|
||||
await db.runAsync(query);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Drug } from "@/redux/features/drug/drug"
|
||||
import { SERVER_IP, SERVER_PORT } from "@/constants/Constants"
|
||||
import axios, { Axios, AxiosResponse } from "axios"
|
||||
import { TempDrug } from "./localdb";
|
||||
|
||||
export const insertDrugServer = async (drug: Drug) => {
|
||||
console.log(`Inserting drug ${drug}`);
|
||||
const response: AxiosResponse<Drug> = await axios.post(`http://${SERVER_IP}:${SERVER_PORT}/drug`, drug);
|
||||
if (response.status === 201) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error("Failed to insert drug");
|
||||
}
|
||||
|
||||
export const getDrugsServer = async () => {
|
||||
const response: AxiosResponse<Drug[]> = await axios.get(`http://${SERVER_IP}:${SERVER_PORT}/drug`);
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error("Failed to fetch drugs");
|
||||
}
|
||||
|
||||
export const editDrugServer = async (drug: Drug) => {
|
||||
console.log(`Editing drug ${drug}`);
|
||||
const response: AxiosResponse<Drug> = await axios.put(`http://${SERVER_IP}:${SERVER_PORT}/drug?id=${drug.id}`, drug);
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error("Failed to edit drug");
|
||||
}
|
||||
|
||||
export const deleteDrugServer = async (id: number) => {
|
||||
console.log(`Deleting drug with id ${id}`);
|
||||
const response: AxiosResponse = await axios.delete(`http://${SERVER_IP}:${SERVER_PORT}/drug?id=${id}`);
|
||||
if (response.status === 204) {
|
||||
return;
|
||||
}
|
||||
throw new Error("Failed to delete drug");
|
||||
}
|
||||
|
||||
export const syncOfflineData = async (offlineData: TempDrug[]) => {
|
||||
const idCounts: Record<number, number> = offlineData.reduce((acc, item) => {
|
||||
acc[item.id] = (acc[item.id] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<number, number>);
|
||||
|
||||
const filteredArray: TempDrug[] = offlineData.filter(item => idCounts[item.id] !== 2);
|
||||
|
||||
const tasks = [];
|
||||
for (const item of filteredArray) {
|
||||
if (item.type === 1) {
|
||||
tasks.push(insertDrugServer(item.drug!));
|
||||
} else {
|
||||
tasks.push(deleteDrugServer(item.id));
|
||||
}
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { SERVER_IP, WS_PORT } from "@/constants/Constants";
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket';
|
||||
import WS from 'ws';
|
||||
|
||||
const ws = new ReconnectingWebSocket(`ws://${SERVER_IP}:${WS_PORT}`, [], {
|
||||
WebSocket: WS.WebSocket,
|
||||
connectionTimeout: 1000,
|
||||
startClosed: true
|
||||
});
|
||||
|
||||
const getSocket = () => {
|
||||
return ws;
|
||||
}
|
||||
|
||||
export default getSocket;
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"build": {
|
||||
"preview": {
|
||||
"android": {
|
||||
"buildType": "apk"
|
||||
}
|
||||
},
|
||||
"preview2": {
|
||||
"android": {
|
||||
"gradleCommand": ":app:assembleRelease"
|
||||
}
|
||||
},
|
||||
"preview3": {
|
||||
"developmentClient": true
|
||||
},
|
||||
"preview4": {
|
||||
"distribution": "internal"
|
||||
},
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
import { Drug } from '@/redux/features/drug/drug';
|
||||
import { deleteDrug, editDrug, getDBConnection, getDrugs, insertDrug, insertTempDrug } from '@/database/localdb';
|
||||
import { setErrorStatus } from '@/redux/features/errorstatus/errorStatusSlice';
|
||||
|
||||
export function* addDrug(action: { type: string, payload: Drug }): Generator<any, void, any> {
|
||||
try {
|
||||
const db = yield call(getDBConnection);
|
||||
const id = yield call(insertDrug, db, action.payload);
|
||||
yield call(insertTempDrug, db, { id: id, type: 1 });
|
||||
const { id: _, ...payloadWithoutId } = action.payload;
|
||||
yield put({ type: "drug/addDrug", payload: { id: id, ...payloadWithoutId } });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
yield put(setErrorStatus({ message: "There has been a Local Database Error", status: true }));
|
||||
}
|
||||
}
|
||||
|
||||
export function* removeDrug(action: { type: string, payload: number }): Generator<any, void, any> {
|
||||
try {
|
||||
const db = yield call(getDBConnection);
|
||||
yield call(deleteDrug, db, action.payload);
|
||||
yield call(insertTempDrug, db, { id: action.payload, type: 2 });
|
||||
yield put({ type: "drug/removeDrug", payload: action.payload });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
yield put(setErrorStatus({ message: "There has been a Local Database Error", status: true }));
|
||||
}
|
||||
}
|
||||
|
||||
export function* updateDrug(action: { type: string, payload: Drug }): Generator<any, void, any> {
|
||||
try {
|
||||
const db = yield call(getDBConnection);
|
||||
yield call(editDrug, db, action.payload);
|
||||
yield put({ type: "drug/updateDrug", payload: action.payload });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
yield put(setErrorStatus({ message: "There has been a Local Database Error", status: true }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function* fetchDrugs(): Generator<any, void, any> {
|
||||
try {
|
||||
const db = yield call(getDBConnection);
|
||||
const drugs = yield call(getDrugs, db);
|
||||
yield put({ type: "drug/fetchDrugs", payload: drugs });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
yield put(setErrorStatus({ message: "There has been a Local Database Error", status: true }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { initDB } from "@/database/localdb";
|
||||
import { call, put, select, takeEvery } from "redux-saga/effects";
|
||||
import { SERVER_IP, SERVER_PORT } from "@/constants/Constants";
|
||||
import axios, { AxiosResponse } from "axios"
|
||||
import { addDrug, fetchDrugs, removeDrug, updateDrug } from "./localdbSaga";
|
||||
import { fetch, NetInfoState } from "@react-native-community/netinfo";
|
||||
import { setConnected, setOnline } from "@/redux/features/status/statusSlice";
|
||||
import { Status } from "@/redux/features/status/status";
|
||||
import { addDrugServer, checkConnection, fetchDrugsServer, removeDrugServer, updateDrugServer, webSocketSaga } from "./serverSaga";
|
||||
|
||||
|
||||
function* initialize() {
|
||||
yield call(initDB);
|
||||
yield call(checkConnection);
|
||||
console.log("Initialized");
|
||||
yield put({ type: 'drug/fetch' });
|
||||
yield webSocketSaga();
|
||||
}
|
||||
|
||||
|
||||
function* fetchOfflineOrOnline() {
|
||||
const status: Status = yield select(state => state.status);
|
||||
if (status.isConnected && status.isOnline) {
|
||||
yield call(fetchDrugsServer);
|
||||
} else {
|
||||
yield call(fetchDrugs);
|
||||
}
|
||||
}
|
||||
|
||||
function* addDrugOfflineOrOnline(action: { type: string, payload: any }) {
|
||||
const status: Status = yield select(state => state.status);
|
||||
if (status.isConnected && status.isOnline) {
|
||||
yield call(addDrugServer, action);
|
||||
} else {
|
||||
yield call(addDrug, action);
|
||||
}
|
||||
}
|
||||
|
||||
function* removeDrugOfflineOrOnline(action: { type: string, payload: number }) {
|
||||
const status: Status = yield select(state => state.status);
|
||||
if (status.isConnected && status.isOnline) {
|
||||
yield call(removeDrugServer, action);
|
||||
} else {
|
||||
yield call(removeDrug, action);
|
||||
}
|
||||
}
|
||||
|
||||
function* updateDrugOfflineOrOnline(action: { type: string, payload: any }) {
|
||||
const status: Status = yield select(state => state.status);
|
||||
if (status.isConnected && status.isOnline) {
|
||||
yield call(updateDrugServer, action);
|
||||
} else {
|
||||
yield call(updateDrug, action);
|
||||
}
|
||||
}
|
||||
|
||||
export function* rootSaga() {
|
||||
yield takeEvery('drug/initialize', initialize);
|
||||
yield takeEvery('drug/fetch', fetchOfflineOrOnline);
|
||||
yield takeEvery('drug/add', addDrugOfflineOrOnline);
|
||||
yield takeEvery('drug/remove', removeDrugOfflineOrOnline);
|
||||
yield takeEvery('drug/update', updateDrugOfflineOrOnline);
|
||||
yield takeEvery('status/checkConnection', checkConnection);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import { deleteDrugServer, editDrugServer, getDrugsServer, insertDrugServer, syncOfflineData } from "@/database/serverdb";
|
||||
import { Drug } from "@/redux/features/drug/drug";
|
||||
import { all, call, put, retry, select, take } from "redux-saga/effects";
|
||||
import { deleteDrug, deleteTempDrug, editDrug, getDBConnection, getTempDrugs, insertDrug, rehydrateLocalDB, TempDrug } from "@/database/localdb";
|
||||
import { SQLiteDatabase } from "expo-sqlite";
|
||||
import { Status } from "@/redux/features/status/status";
|
||||
import { SERVER_IP, SERVER_PORT } from "@/constants/Constants";
|
||||
import { setOnline, setConnected } from "@/redux/features/status/statusSlice";
|
||||
import { NetInfoState, fetch } from "@react-native-community/netinfo";
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
import { io, Socket } from "socket.io-client";
|
||||
import { store } from "@/redux/store";
|
||||
import { EventChannel, eventChannel } from "redux-saga";
|
||||
import { PayloadAction } from "@reduxjs/toolkit";
|
||||
import getSocket from "@/database/socket";
|
||||
import { setErrorStatus } from "@/redux/features/errorstatus/errorStatusSlice";
|
||||
import ReconnectingWebSocket from "reconnecting-websocket";
|
||||
|
||||
|
||||
export function* fetchDrugsServer() {
|
||||
try {
|
||||
const drugs: Drug[] = yield call(() => getDrugsServer());
|
||||
console.log(drugs);
|
||||
yield call(rehydrateLocalDB, drugs);
|
||||
yield put({ type: "drug/fetchDrugs", payload: drugs });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
|
||||
}
|
||||
}
|
||||
|
||||
export function* addDrugServer(action: { type: string, payload: Drug }) {
|
||||
try {
|
||||
const drug: Drug = yield call(() => insertDrugServer(action.payload));
|
||||
const db: SQLiteDatabase = yield call(getDBConnection);
|
||||
console.log(drug);
|
||||
yield call(() => insertDrug(db, drug));
|
||||
yield put({ type: "drug/addDrug", payload: drug });
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
|
||||
}
|
||||
}
|
||||
|
||||
export function* removeDrugServer(action: { type: string, payload: number }) {
|
||||
try {
|
||||
yield call(() => deleteDrugServer(action.payload));
|
||||
const db: SQLiteDatabase = yield call(getDBConnection);
|
||||
yield call(() => deleteDrug(db, action.payload));
|
||||
yield put({ type: "drug/removeDrug", payload: action.payload });
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
|
||||
}
|
||||
}
|
||||
|
||||
export function* updateDrugServer(action: { type: string, payload: Drug }) {
|
||||
try {
|
||||
yield call(() => editDrugServer(action.payload));
|
||||
const db: SQLiteDatabase = yield call(getDBConnection);
|
||||
yield call(() => editDrug(db, action.payload));
|
||||
yield put({ type: "drug/updateDrug", payload: action.payload });
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function* checkConnection() {
|
||||
try {
|
||||
yield put(setOnline(false));
|
||||
yield put(setConnected(false));
|
||||
const internetConnection: NetInfoState = yield call(fetch);
|
||||
if (!internetConnection.isConnected) {
|
||||
console.error("No internet connection");
|
||||
return;
|
||||
}
|
||||
yield put(setOnline(true));
|
||||
console.log("Internet connection available");
|
||||
const response: AxiosResponse = yield call(axios.get, `http://${SERVER_IP}:${SERVER_PORT}/`, { timeout: 5000, validateStatus: () => true });
|
||||
if (!(response.status === 200)) {
|
||||
console.error("Connection to server failed");
|
||||
return;
|
||||
}
|
||||
console.log("Connection to server successful");
|
||||
yield put(setConnected(true));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
yield put(setErrorStatus({ message: "There has been a Network Error", status: true }));
|
||||
}
|
||||
}
|
||||
|
||||
export function webSocketChannel() {
|
||||
const socket = getSocket();
|
||||
return eventChannel((emit) => {
|
||||
console.log("Websocket saga started");
|
||||
socket.onopen = () => {
|
||||
console.log("Connected to WebSocket server!");
|
||||
emit({ type: "connect" });
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("Received message:", data);
|
||||
emit({ type: data.event, payload: data.data });
|
||||
}
|
||||
|
||||
socket.onclose = () => {
|
||||
console.log("Disconnected from server.");
|
||||
emit({ type: "disconnect" });
|
||||
};
|
||||
|
||||
return () => {
|
||||
socket.close();
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function* webSocketSaga() {
|
||||
const channel: EventChannel<PayloadAction> = yield call(webSocketChannel);
|
||||
const socket: ReconnectingWebSocket = yield call(getSocket);
|
||||
socket.reconnect();
|
||||
while (true) {
|
||||
const action: PayloadAction<Drug | { id: number } | void> = yield take(channel);
|
||||
console.log(action);
|
||||
const db: SQLiteDatabase = yield call(getDBConnection);
|
||||
switch (action.type) {
|
||||
|
||||
case "connect":
|
||||
console.log("Connected to server via WebSocket");
|
||||
yield put({ type: "status/checkConnection" });
|
||||
const tempData: TempDrug[] = yield call(getTempDrugs, db);
|
||||
yield call(() => syncOfflineData(tempData));
|
||||
yield call(() => fetchDrugsServer());
|
||||
yield call(deleteTempDrug, db);
|
||||
break;
|
||||
case "itemAdded":
|
||||
yield call(() => insertDrug(db, action.payload as Drug));
|
||||
yield put({ type: "drug/addDrug", payload: action.payload as Drug });
|
||||
break;
|
||||
case "itemUpdated":
|
||||
yield call(() => editDrug(db, action.payload as Drug));
|
||||
yield put({ type: "drug/updateDrug", payload: action.payload as Drug });
|
||||
break;
|
||||
case "itemDeleted":
|
||||
yield call(() => deleteDrug(db, (action.payload as { id: number }).id));
|
||||
yield put({ type: "drug/removeDrug", payload: (action.payload as { id: number }).id });
|
||||
break;
|
||||
case "disconnect":
|
||||
console.log("Disconnected from server. Reconnecting...");
|
||||
yield put({ type: "status/checkConnection" });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "ma-nonnative",
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"reset-project": "node ./scripts/reset-project.js",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"test": "jest --watchAll",
|
||||
"lint": "expo lint"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@pchmn/expo-material3-theme": "^1.3.2",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-navigation/bottom-tabs": "^7.0.0",
|
||||
"@react-navigation/native": "^7.0.0",
|
||||
"@reduxjs/toolkit": "^2.3.0",
|
||||
"axios": "^1.7.9",
|
||||
"expo": "~52.0.11",
|
||||
"expo-blur": "~14.0.1",
|
||||
"expo-build-properties": "^0.13.2",
|
||||
"expo-constants": "~17.0.3",
|
||||
"expo-dev-client": "~5.0.6",
|
||||
"expo-font": "~13.0.1",
|
||||
"expo-haptics": "~14.0.0",
|
||||
"expo-linking": "~7.0.3",
|
||||
"expo-router": "~4.0.9",
|
||||
"expo-splash-screen": "~0.29.13",
|
||||
"expo-sqlite": "~15.0.3",
|
||||
"expo-status-bar": "~2.0.0",
|
||||
"expo-symbols": "~0.2.0",
|
||||
"expo-system-ui": "~4.0.4",
|
||||
"expo-web-browser": "~14.0.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.3",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-paper": "^5.12.5",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "^4.0.0",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-webview": "13.12.2",
|
||||
"react-redux": "^9.1.2",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"redux-saga": "^1.3.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "~18.3.12",
|
||||
"@types/react-native-sqlite-storage": "^6.0.5",
|
||||
"@types/react-test-renderer": "^18.3.0",
|
||||
"@types/ws": "^8.5.14",
|
||||
"jest": "^29.2.1",
|
||||
"jest-expo": "~52.0.2",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true,
|
||||
"expo": {
|
||||
"doctor": {
|
||||
"reactNativeDirectoryCheck": {
|
||||
"exclude": [
|
||||
"@pchmn/expo-material3-theme",
|
||||
"@reduxjs/toolkit",
|
||||
"react-redux"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||