Anul 3 Semestrul 1
@@ -77,3 +77,131 @@ __pycache__/
|
|||||||
*.gcno
|
*.gcno
|
||||||
*.gcda
|
*.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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||